Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

goagent

  • Loading branch information...
commit fa9959e577395e48a477fd5495afbc2363a51baa 0 parents
phus_lu authored
20 local/CA.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDUjCCAjoCAQAwDQYJKoZIhvcNAQEFBQAwbzEVMBMGA1UECxMMR29BZ2VudCBS
+b290MRAwDgYDVQQKEwdHb0FnZW50MRMwEQYDVQQDEwpHb0FnZW50IENBMREwDwYD
+VQQIEwhJbnRlcm5ldDELMAkGA1UEBhMCQ04xDzANBgNVBAcTBkNlcm5ldDAeFw0x
+MTA0MjAxNzM3MzVaFw0zMTA0MjAxNzM3MzVaMG8xFTATBgNVBAsTDEdvQWdlbnQg
+Um9vdDEQMA4GA1UEChMHR29BZ2VudDETMBEGA1UEAxMKR29BZ2VudCBDQTERMA8G
+A1UECBMISW50ZXJuZXQxCzAJBgNVBAYTAkNOMQ8wDQYDVQQHEwZDZXJuZXQwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0jV3yx3yGAHlQqzm4fbVascvT
+nyCdtParWBnQn5A3U9pJjI47SCo8j7FfeoYSL0mHbJ0mjafTnw+/ewb09AQIkdEl
+n6smojl7NOKs1Yhh0yldB6kQWiBPr/XKMBskmvcyjJEqkU6hwtibASaAZt+q5clT
+BJ2XRaeAaMDeDbYDchFa7MTNhoQMdQFu1UhqkJxtuVMBEs1/qPbx5O9pqy1RgAeK
+WvxyCzVRi2hHaTns+weZBJ6N71afyvr1etGqqtWVpjpobk1ZFBYk4xpznCbm4iqP
+Ar9nqdGDw1IJIdX0DyMJIJrpwOf94pAK9v6zG0jnsbMqromL18kEMXZgYSMlAgMB
+AAEwDQYJKoZIhvcNAQEFBQADggEBAASiRZFCcgQ8VsncB8wKG+bmN9UZhXLJYRGp
+m3KIUy/zG6mMWG/3TgkPn8ivNAkrk+1ul5SrRvot/Q7XWpb0/yKX0faX/512JF2G
+220gopqo4amj+g7SBKxzW8VhLQF6dm99eUd27JbAzi5VKXR0dMFECk2rFlA5gAR5
+zzFijaXHuObMtd2S292wji79JWocA0z6WVM5Qokw4hRTsXWfXL0BJTL3i/xRrEzW
+sdecYFpNhaEKldjegazoqAqiAMJj7PDU1AqdprNsq+3/tAmCvn0URkas4QhkvtqS
+FO6OGm/PZe5GbkBpAKdfLYFfEMO17SAGHHqAsIKAFfuHYONRGSM=
+-----END CERTIFICATE-----
27 local/CA.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAtI1d8sd8hgB5UKs5uH21WrHL058gnbT2q1gZ0J+QN1PaSYyO
+O0gqPI+xX3qGEi9Jh2ydJo2n058Pv3sG9PQECJHRJZ+rJqI5ezTirNWIYdMpXQep
+EFogT6/1yjAbJJr3MoyRKpFOocLYmwEmgGbfquXJUwSdl0WngGjA3g22A3IRWuzE
+zYaEDHUBbtVIapCcbblTARLNf6j28eTvaastUYAHilr8cgs1UYtoR2k57PsHmQSe
+je9Wn8r69XrRqqrVlaY6aG5NWRQWJOMac5wm5uIqjwK/Z6nRg8NSCSHV9A8jCSCa
+6cDn/eKQCvb+sxtI57GzKq6Ji9fJBDF2YGEjJQIDAQABAoIBACB3n2JN/xV1tlsM
+P1fuuxLxD+8hGVNivEy5jgLW/q8EVCePr+/3HSlAyauas8tHV5iTrnrFVF2Yp9NO
+A0U/MA5+cjaqzLMozt9Z9j0QNPMqbrC89Ojs3AyYXsGZ/veJKlSbtGsMMDCkgiD1
+hv/l/+iSY66bEN+n9eQAclY77vQVXLSoCMReVfbdUxU9Q1MywODGf5Kng84gTyT/
+zd+xEfFHz8zbCDyw3Hd3hGJ2FxN+yFz1uI29ORb3/R7N9dZgsWf2fsfiRVPGuhAH
+RNlDockImB+BKeidx14sMim5p7s8heVYkBVW3SIOEReqz59b8x4QVhhZrzYWSHNq
+Gi0pLiECgYEA26v6b+rsxT//PznJSEhLyrg1Jo6XeWmFlwZY0KoipH6sxX/YPrDZ
+bOPN8KvAHtRltRLFs3L2iRaO2jltjxHGVF4FSYrf5KSExuj6/ABHxWM0YtezfDwR
+hU1ORg5QwVegMoOgsphS8ts2xn6T6wIwpBgtFPY84A52IBVn5CHuQtkCgYEA0mk5
+EpnZfmMT5ldcZ7JlZrxfWKvDHIcuA0neIBsd4oIcEfRhDC3TolH6pB4z4SCqyYw3
+t5HMiTx8yz074mycTcOcXO1Cs49kMZwbzKziRXpUdCW4EIo0DG+6LqwetPgYzozg
+FeTiGQBHqjrzjBLZ3RfozICbo7dvYHkVLK92my0CgYBWNBjlDnW3ujN6Jj0cxnIn
+rT3+UXqTxJsN9wmnaPyLPMKkBlVf1JqeJo9MYLnV31fCRQmcMAMbLOUGMf8SY9FG
+jlbY00ylNwJ75DWJ6ro/dXy7RRZELHZbr0iGKVv7Y12UNR88tpXmg6vtHQMC+CsK
+Wgpm7XJaIpKsaHoKhl4vkQKBgBBBTsZwGkxYTSZDY4EjWBAax2brRhSDIPviDgX+
+8k0YbiC493Jga/QjTzC0oJ9ozajqazeETP/hK2bsIR858s1TKlZHghqrHjty6vbh
++E0TyUh7zX+BncnEK+cFJw4mCIyUd49ZcloqGl89VKlin3AkM7jwypVYS4Nxd0BP
+geM1AoGBALOWNmYm9d4gRhUv14oJRiA+e+4evswiWvVdnS6UJ4tst0NlEKWahtpR
+kdAjav8WV1n6IbkJC2L743Ozjb63z5w6p5O7OtTyYUWbLt1hvNkHlkNP8AjRQP8E
++N2jjrMAdbEwahPNAX9QlzHpF62AfEGQ3oODUm06TGTq+yAPSyYm
+-----END RSA PRIVATE KEY-----
1  local/CA.srl
@@ -0,0 +1 @@
+7
13 local/Microsoft.VC90.CRT.manifest
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!-- Copyright (c) Microsoft Corporation. All rights reserved. -->
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <noInheritable/>
+ <assemblyIdentity
+ type="win32"
+ name="Microsoft.VC90.CRT"
+ version="9.0.21022.8"
+ processorArchitecture="x86"
+ publicKeyToken="1fc8b3b9a1e18e3b"
+ />
+ <file name="msvcr90.dll" />
+</assembly>
BIN  local/certadm.dll
Binary file not shown
18 local/certs/api.twitter.com.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC3TCCAcUCAQMwDQYJKoZIhvcNAQEFBQAwbzEVMBMGA1UECxMMR29BZ2VudCBS
+b290MRAwDgYDVQQKEwdHb0FnZW50MRMwEQYDVQQDEwpHb0FnZW50IENBMREwDwYD
+VQQIEwhJbnRlcm5ldDELMAkGA1UEBhMCQ04xDzANBgNVBAcTBkNlcm5ldDAeFw0x
+MTA2MjcwNjUwMjRaFw0zMTA2MjcwNjUwMjRaMH4xFzAVBgNVBAsTDkdvQWdlbnQg
+QnJhbmNoMRgwFgYDVQQKEw9hcGkudHdpdHRlci5jb20xGDAWBgNVBAMTD2FwaS50
+d2l0dGVyLmNvbTERMA8GA1UECBMISW50ZXJuZXQxCzAJBgNVBAYTAkNOMQ8wDQYD
+VQQHEwZDZXJuZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOm3oz6wZgpw
+ZyYkmJtaiaMDVpaFIXygCiiHojoDTTyGCGQj8+hzKF723C2Sz9VATI/L4rmdB6Yd
+fzCpv254jhKlPcBeaVmE5fs/QZtCD5bQkkTnT2f661bEuvW3RPzuiasfvDTaxsPQ
+jas+Wq1rYx2MFFDG/k6re45rtzcd7b8pAgMBAAEwDQYJKoZIhvcNAQEFBQADggEB
+AJYa2fUTMFthbXKcT9asvP3w+9nRAATUFLmcL5ai0ckx7Unn4xGCYafzvz7jRSS3
+KE70ZJJV17ZBgR//xck+RCpAykJx9t2HSEEd2VGDtcErJsZqRW2B27d3sSSfVjwU
+k1trUCB7lsQXys3o5yAvH+QTVNeXRemxXbWgyx74zjm5VijLNW86BJV3Afb37o6J
+if19cmjlez623oV1nrWEnW7FONR0H+Q9c4luf5ObhZ20F/C+8RPbZvKc745NtD46
+3jMjL039QlLqofOwgosqWoNVPQKliktHpfPeEVLwbGT9JgAK9hDbodW2Q/9rf2Kw
+1xgqfGKkwRjHEqaWH6hHvYc=
+-----END CERTIFICATE-----
15 local/certs/api.twitter.com.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDpt6M+sGYKcGcmJJibWomjA1aWhSF8oAooh6I6A008hghkI/Po
+cyhe9twtks/VQEyPy+K5nQemHX8wqb9ueI4SpT3AXmlZhOX7P0GbQg+W0JJE509n
++utWxLr1t0T87omrH7w02sbD0I2rPlqta2MdjBRQxv5Oq3uOa7c3He2/KQIDAQAB
+AoGAN8APV7faT2kqocWfBYfeRx1XvUX6i/DdG3fnMbFx7hsGy2RsHSnDGowGT3Sy
+0OqqmK6I4b/cgDqPCXukZ2SodsNa00ZXZozdkPW5kRMmewkkR0KvaWl7PBLT2vLy
+T2Cao+EwQepc9DMDtEpqvg+h939eeZTvzJj2kfVWkas6LAUCQQD+xp34g5fr8c+E
+epM9ywEw13XHGcYD1RINXgV4Cdde6BMAwUEgfM116wgjBOiMQGLQxCl17tkS5aLN
+yczOHn4TAkEA6tceL9Brq5ZblzvXhEiLWKNWE+7jb8fzfgAb15XlJAsLlpoWrfGf
+f5BbYS0us3pSCXENkUNP621Xx4NI6KmFUwJBAKTzdQ2ULYU+XuvX7ILCb3fu17tb
+bX/HsNNkv0ezn3Q77ym69W6SPvgMfo0lvWYovZGKn4lSZcq9UIXKFMaSqW8CQDG7
+otnvYo3xBq6NbsPF9TStSpJoGeRXTPqXHU5XoTIz4J1hPoJR1DxYnvGCdwxtUChN
+t9jWj3tFKbzH0c3r8J8CQQCOUdCcTnXrWSg0Wdx0mKS8prT7cxkKtyZoh6JaMWPy
+sa1Eun10hdOoERYfGZix6LEGrDqKfEClpTClK9KtE/fF
+-----END RSA PRIVATE KEY-----
18 local/certs/facebook.com.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC1zCCAb8CAQQwDQYJKoZIhvcNAQEFBQAwbzEVMBMGA1UECxMMR29BZ2VudCBS
+b290MRAwDgYDVQQKEwdHb0FnZW50MRMwEQYDVQQDEwpHb0FnZW50IENBMREwDwYD
+VQQIEwhJbnRlcm5ldDELMAkGA1UEBhMCQ04xDzANBgNVBAcTBkNlcm5ldDAeFw0x
+MTA2MjcwNjUwMjVaFw0zMTA2MjcwNjUwMjVaMHgxFzAVBgNVBAsTDkdvQWdlbnQg
+QnJhbmNoMRUwEwYDVQQKEwxmYWNlYm9vay5jb20xFTATBgNVBAMTDGZhY2Vib29r
+LmNvbTERMA8GA1UECBMISW50ZXJuZXQxCzAJBgNVBAYTAkNOMQ8wDQYDVQQHEwZD
+ZXJuZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKx8g5jz95JjP/H25bv7
+rNKjEIw4lUPk5HfysoLliMzPAETh4LGGE2pNX7cS7mxhS//SipRaQYTvPoj1Zn/H
+MbRpa+T/Ofl5ZfubLuxQeJwh/O7+Nk7CcTAhdIpxxQS0NGIoFpoNi1p17BlDy+bV
+Ym9IssjSOP10NgfF2pzfolcdAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAD/HJ56M
+utP5YDjsvEUrZN1lh0zqgmtjB+Ju16CIgTplnCU3JW6Am+dJ6bJhM1M1mURI/rKv
+0DS0WkBEO3yKqMSp+XN7ja2r1zqavgo6mpcH5QtcwksiQQQgx6MObI6B8e4k8lW4
+9u5NiwfC59tfjMlTZ8R1z7ZX8g5Og2jBbWLnUf0GoXnNTr4W8YYjcmA4/fNIlUf8
+T1lnGly9Bpoe2W/AmBWxUIdSfF6uUm2dwLjd5I5kva3JiEVVK65UWbwB5Kse3owp
+SVj0o9V+3274AlVwY86H2dcNUIKqrODlzmgxFjPkCxxzLXF0Cf0Nr+kscDZs5ULk
+hbtzStUXyE+y6Gw=
+-----END CERTIFICATE-----
15 local/certs/facebook.com.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCsfIOY8/eSYz/x9uW7+6zSoxCMOJVD5OR38rKC5YjMzwBE4eCx
+hhNqTV+3Eu5sYUv/0oqUWkGE7z6I9WZ/xzG0aWvk/zn5eWX7my7sUHicIfzu/jZO
+wnEwIXSKccUEtDRiKBaaDYtadewZQ8vm1WJvSLLI0jj9dDYHxdqc36JXHQIDAQAB
+AoGBAJ8ZEsFodzldtsPW+rzUPerkYgUUxAml/V7uS94VHoPbg+IFfx4AD7SDvf+9
+xJHPhSxo5U5Fmh2mF8Z4pJzD/YFpnTJkkpzUguQIyMxfz6U1Tz7ZAv//p9gx0T7E
+4aiXDhoWErdZoWlAKjQnHqIt7EN+w2ec5WbKNLbQeZSvgXu5AkEA249pAVkVdgHh
+h7RAUp09HagK2VsGUy1T3HYSSysC78cqzyP2UyOjk60uhU3vtjryE6zMvx08GGeF
+nseGBHlh6wJBAMkdDLdhR1EnTaIZSh7SQwUo483YzAknWgfaU4BattlLFiUidD6z
+SWnMhbpA+ij13e+8jMExTcgAizF/zYdB4RcCQGalWjMOIdFkubqG05eL+VmXCVNq
+kb9rLoygCpdnyVyuIV5r8qoVvFC7I0ehExN/5VK7c5FZqpHCPB7qKsaGtPcCQAfU
+0cHyT8yhMyy0v2Yj2ehBh11kq81PcvoLTmUZIzWi7uHTTaODZ8Bu7mYqUZN2v4Wu
+WezL9rNrEgCDcYVq01ECQHbnjXdLJObD5SghK/CopgF/GqnpCnQ7IV5nuVkUsdE9
+IYohUUZb/QJ5hep34WSYTTvq/lLbBccRYTajZcN4JOE=
+-----END RSA PRIVATE KEY-----
18 local/certs/scribe.twitter.com.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC5DCCAcwCAQYwDQYJKoZIhvcNAQEFBQAwbzEVMBMGA1UECxMMR29BZ2VudCBS
+b290MRAwDgYDVQQKEwdHb0FnZW50MRMwEQYDVQQDEwpHb0FnZW50IENBMREwDwYD
+VQQIEwhJbnRlcm5ldDELMAkGA1UEBhMCQ04xDzANBgNVBAcTBkNlcm5ldDAeFw0x
+MTA2MjcwNzA3MjNaFw0zMTA2MjcwNzA3MjNaMIGEMRcwFQYDVQQLEw5Hb0FnZW50
+IEJyYW5jaDEbMBkGA1UEChMSc2NyaWJlLnR3aXR0ZXIuY29tMRswGQYDVQQDExJz
+Y3JpYmUudHdpdHRlci5jb20xETAPBgNVBAgTCEludGVybmV0MQswCQYDVQQGEwJD
+TjEPMA0GA1UEBxMGQ2VybmV0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDl
+VovqVtQLiOlfFgrtYgmG4U/m0VpXn7TSLR9bG4XU9gAua9FI7a2RSO6QLC0Er5Om
+HGp/Kyr7BLwZsuDVYtxRGKFLAzy/NkR7v8874CYMLIlPlanOK5KVNB3xJC8A2bhW
+I5EM6Keu/YMLjUPHPNUTvY+oS2ET3qfW1DiEzKqH+QIDAQABMA0GCSqGSIb3DQEB
+BQUAA4IBAQCNf7k0dZN1zA10z66tZUhmoR+rxhet1bZuu4zjydhydH/e2is87K6F
+hOz0zPXg0V4QZPPTtssU3jjUGuisOWyZfd+sg29dY6NZDRKxXbBhLtyXBonLcN62
++sqPYl+1WR5AGEt/879Fwf4GieJ6yjr2mGo7EhjF7C3S8bW//RwQlryqOPlX56dX
+bISrlRfRbLOejeu0UCHHLaNOAP5NVzZHf/Df4rS0SGwcH+FaYLbvWwxd5b1PQemK
+6rPRzfT8pZ5JNq0ec2ZFb4x3vBDxdwt8psfPgma6JWgHaoj+WeB/Y6Oi2Q+0y4w1
+wk2ipdvW1wYHn5FZOp5Ynb4WY/Tn1jta
+-----END CERTIFICATE-----
15 local/certs/scribe.twitter.com.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDlVovqVtQLiOlfFgrtYgmG4U/m0VpXn7TSLR9bG4XU9gAua9FI
+7a2RSO6QLC0Er5OmHGp/Kyr7BLwZsuDVYtxRGKFLAzy/NkR7v8874CYMLIlPlanO
+K5KVNB3xJC8A2bhWI5EM6Keu/YMLjUPHPNUTvY+oS2ET3qfW1DiEzKqH+QIDAQAB
+AoGAJdz4FKRpNc5Q2UYGX9LDx+UDEBSYWccgT2Lrvlr46YQD26YpU2UKNuZXnK3u
+cMucENy4KG60FeVeOM/zlsdPoDPxp+TnC73GDEN6ThXUhvDyLFMAh3gZHa6ajFKo
+gyMqAQN7WOTtsPpEMjeIgHxhFt7wY91wm+vBSM6C0Itu6kECQQD0tuaDqEZQph7A
+v/ABoXd/RUK1+qzsg5tzWOHgBiz8k5ly1NM+6D5N90OQzF/8D2Jg8IRVozp4BfRP
+rdj15foFAkEA7+ocy7H8DEiqkI37U+ddwEMS5yPzhURnotu0MceXHOdP0dm+b5P0
+nUC4eMIrCpnkd2wQO0pbVRh0dO9j3BSUZQJBAJLvq5PMG4RbasXDueHQyQazWK4a
+OXC+ST3GVcIFE0gJfC7WGY2BN7/qwzgTb2LS/fhFEsC1BOuCb8LUGRz1HN0CQQC/
+4554hbk1EgHqcMVujIV9u6go26ZxFw9VQSjVD/Cbm59KF0Cr+ckS5asmWxcV+ZS0
+t+gbpBN88nxi8v4KDyPJAkBbwJOiFloGe/HuL0rqHOP2XXCv6bMoLFspS5L6XwIS
+JmuRFw26ELw3wpaZ0Jm+GWHKRdJiahNGjF/0qyGlg6Bm
+-----END RSA PRIVATE KEY-----
18 local/certs/si0.twimg.com.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC2TCCAcECAQcwDQYJKoZIhvcNAQEFBQAwbzEVMBMGA1UECxMMR29BZ2VudCBS
+b290MRAwDgYDVQQKEwdHb0FnZW50MRMwEQYDVQQDEwpHb0FnZW50IENBMREwDwYD
+VQQIEwhJbnRlcm5ldDELMAkGA1UEBhMCQ04xDzANBgNVBAcTBkNlcm5ldDAeFw0x
+MTA2MjcwNzA3MjNaFw0zMTA2MjcwNzA3MjNaMHoxFzAVBgNVBAsTDkdvQWdlbnQg
+QnJhbmNoMRYwFAYDVQQKEw1zaTAudHdpbWcuY29tMRYwFAYDVQQDEw1zaTAudHdp
+bWcuY29tMREwDwYDVQQIEwhJbnRlcm5ldDELMAkGA1UEBhMCQ04xDzANBgNVBAcT
+BkNlcm5ldDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyM8Zj2wABVBk8XUm
+Gucnn5Bp3/wKe6RzEG+GGfB79GVL95xB3M1yeNncy9by3p+4eIxJ9V7IbdNWyApg
+S9YxJXNuWZoEuu84vdoSk9wEg1cU5SrEVSoYrAEHSp9NISUGw4TlNtSgjeJTfRjm
+PEWUhokDSf0gwkoVDrr+svugHSMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAhp9a
+lhPwewn1yBmkNfHNkOCahRDLQKRSk6yxrro2ApbGrsEfIwPVE2aTmg9YAwn5pXZB
+7vsVrkr0PrZDDajAjynaJ7T9GX5p80ZSmcblVy6/4owuU1d8K3+jkHTMEE7qLica
+B4s7oX2iEUy+1whUGm6gx/NmACrBmxV1m3M9d42E+HXEa0fP0FD0t+rW/bo0W/ly
+GTSpxFMlh0crein/GmpBiM2/LdvfMbVNCHihX8iAwkhhrfy69M3Q9dbIVR+GZBmF
+H64KyTdeJTCvIOhgnyJOhWkjreXxo9DbSzZqDgl1oeXvKMdhOP/Cfg12G4Eqc6pd
+6omJIe85NUHLz7rRpQ==
+-----END CERTIFICATE-----
15 local/certs/si0.twimg.com.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDIzxmPbAAFUGTxdSYa5yefkGnf/Ap7pHMQb4YZ8Hv0ZUv3nEHc
+zXJ42dzL1vLen7h4jEn1Xsht01bICmBL1jElc25ZmgS67zi92hKT3ASDVxTlKsRV
+KhisAQdKn00hJQbDhOU21KCN4lN9GOY8RZSGiQNJ/SDCShUOuv6y+6AdIwIDAQAB
+AoGBAI6stth0P7+G03HLbnuG6Vwx20fNaBVZTnfLBVjAyRFoN4WCfDlJr5+2C265
+Fm6exQ7BfnrcEUQW+H3BAZJgaSGUFOhb6FXt8XdMvgOODEYF6eRPbJG+3pXeNg3r
+aBakOPv+gtU8JXz+3ys8OK/rRGcl3nve1bGd1Xlg0yqRG4khAkEA8JEY++orcZDJ
+WG/8494OL24NcXfeY99i1NmOyModqYrOkMBgaQLXz5hfYfXcYIYJ7K4u+HoPhtGM
+4H/VQMaifQJBANWxDC+dryVMcVaoMiOguoDToGibpeHz8YLYtwOJ7fkk6hIf2NKy
+LcT9sacr8rVguICXr3lIui0oEQabZGF3MB8CQD4Z1s9BNhHNBI0V67yPGC5aRJIk
+FX7GreawetGOi3W25XgskTbKixeGCClrpIYuU+WLWYi/Sb7N3YYeudhM7qkCQQDD
+MNiXueodIMk6RaKe5pbVS/lu9BW+4gvN4FSzl87W0AA7E/oC4xxpnu4ibENjp/iA
+BY4UM/lTfBCpP1GesbczAkEA5gdup7Ve1UfVox0bxy09IXlljKFRfosDXglkx3v4
+488chb+B9+JQE5rwKGhqJAsKjKr0up9EhT3Z2ctvF5HuIw==
+-----END RSA PRIVATE KEY-----
18 local/certs/twitter.com.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC1TCCAb0CAQIwDQYJKoZIhvcNAQEFBQAwbzEVMBMGA1UECxMMR29BZ2VudCBS
+b290MRAwDgYDVQQKEwdHb0FnZW50MRMwEQYDVQQDEwpHb0FnZW50IENBMREwDwYD
+VQQIEwhJbnRlcm5ldDELMAkGA1UEBhMCQ04xDzANBgNVBAcTBkNlcm5ldDAeFw0x
+MTA2MjcwNjUwMjRaFw0zMTA2MjcwNjUwMjRaMHYxFzAVBgNVBAsTDkdvQWdlbnQg
+QnJhbmNoMRQwEgYDVQQKEwt0d2l0dGVyLmNvbTEUMBIGA1UEAxMLdHdpdHRlci5j
+b20xETAPBgNVBAgTCEludGVybmV0MQswCQYDVQQGEwJDTjEPMA0GA1UEBxMGQ2Vy
+bmV0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDqVNsehuczsaMhJAMn7mUx
+UTJSJtoeBS/cpdtP7fLz8kLFYfiRUQ5feoSiJ0lt54MBek+D2AnXIiH+Rkxgt1LA
+gULEkXhlUBFO767cGf7SFR7BtN2t2LnZzhWo6HeoatqTRJgRKSXkUfFCjq3Ogdha
+Bd/Vo87zB2Wp9RPFlaCjRQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBhvObxDwEl
+b2saOHKLNqvqGz9QuvoKIBXh5pAvBtyEMNdd1bd3eJFO6yHKV9R0xCoLjCmHLnHd
+nQsP+KeWEz5S/vQ8StM2apuyDF3DRg/6Bf3gE2kzC+5WPhdMP5QkSANv+KuEFCoa
+i369ektO129mtMcbeAa2xsG3+XS/uvm2d+D8Pxk4VgvyT8DRgW+eVcAAKoBePUdz
+cDAeoGeUIuWp6Z1dNZRU/6L2gjrwl3UeyEDa3Vy1kZdEukBDXfz0NvVT0kKcG7Mo
+UbHHpJk60rPEadXoXR+trhCoxQK8mDIbtGAQhUD+9kl0luu19isFlqVB4mzEMWg6
++Ic2SBT/8ies
+-----END CERTIFICATE-----
15 local/certs/twitter.com.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDqVNsehuczsaMhJAMn7mUxUTJSJtoeBS/cpdtP7fLz8kLFYfiR
+UQ5feoSiJ0lt54MBek+D2AnXIiH+Rkxgt1LAgULEkXhlUBFO767cGf7SFR7BtN2t
+2LnZzhWo6HeoatqTRJgRKSXkUfFCjq3OgdhaBd/Vo87zB2Wp9RPFlaCjRQIDAQAB
+AoGAMq0pOf/YrkBDVB5ypEcVEUZLCKMoVoeuoxwnQrDl8sW4lPEsDVknL5TSbDLs
+2VzQ4xftbEjjT+gsy82uJNhgh6EkR499w0HbnAgh63zAC6l3fH0Snp5r+kadXQkR
+hyOH9tdEvqhs5WWg1cheAXyX40aWajtGvTlOfWiQUxji2t0CQQD/kqjvqoyBhKiv
+TzyYUFUPQMR6UvN5fpZlPfUMa7UUcbixs0qt5JmOoaFNjRHBjkiW+IVTDypb4TW3
+cVr4iI87AkEA6rkbwzth7R0USv7CvAOOLE0uzNinv7FOq3tRszcZH41Hd+8N4Sdb
+g/YtShc4k6+sDsxsAsYYwiMHryBDnOVvfwJBAO3bTwMPVYlNL7lJI3oWebOicbnu
+7c7LM6myChivPW5zkJNB7GT+9rM6VmdYvYfIpmJrlZoB6Uhtx5KdGy9n2PUCQH1N
+uF5u0wYr4etvOTH5i+pmgbdlaZfR5bdxq9nKbiUD3MVP3s71nctCnkbRQqjEvUth
+IcARVKI+5Rk+Vhnp+a0CQQCXaBcAcCuqH8fUj4XpcorKWU4OeflkXCtm3KnSZ67d
+Uywjdjxv1pKPIb0QE0Ez8Msj8RLjafsWtqwzV/MKNHSn
+-----END RSA PRIVATE KEY-----
18 local/certs/www.facebook.com.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC4DCCAcgCAQUwDQYJKoZIhvcNAQEFBQAwbzEVMBMGA1UECxMMR29BZ2VudCBS
+b290MRAwDgYDVQQKEwdHb0FnZW50MRMwEQYDVQQDEwpHb0FnZW50IENBMREwDwYD
+VQQIEwhJbnRlcm5ldDELMAkGA1UEBhMCQ04xDzANBgNVBAcTBkNlcm5ldDAeFw0x
+MTA2MjcwNjUwMjVaFw0zMTA2MjcwNjUwMjVaMIGAMRcwFQYDVQQLEw5Hb0FnZW50
+IEJyYW5jaDEZMBcGA1UEChMQd3d3LmZhY2Vib29rLmNvbTEZMBcGA1UEAxMQd3d3
+LmZhY2Vib29rLmNvbTERMA8GA1UECBMISW50ZXJuZXQxCzAJBgNVBAYTAkNOMQ8w
+DQYDVQQHEwZDZXJuZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMy91EcC
+BPy4a8c/Z6WiPkympjp+RU0ErSxtCADEf0JFvZtqCrQiUpPjt4rZQd6ShOXUd1tN
+3FWUvwF5IcHnKqhbtkrTZdbcNzBIqNoFFaItJbyA4Hd7DKjpqosYcolo2sxukM3g
+xOVwY0bu6cvqMLAyxNTP0Up+3nlSmLOHm/bJAgMBAAEwDQYJKoZIhvcNAQEFBQAD
+ggEBAGR6rWzMZuqLd8/G40OD6iTrK2rKhYrVeh/thlLC4pEFG0kY2luM2FwIMvrk
+SiJDf1MxVfSJMp7kk0lN/LMss53DfZOuxaFgdm16RSF0Ouq8gidpQZCpKzfQ3t5v
+9bSfOp+5bu8zFAsrfjJeh0gth13ClrRQyEI53i9SM/E+UGMOkSb1PutyWCkBL4c/
+x1TfXdepQwgi0W9/Uo/FiVAdyrV0lsXlpj1nv6Rvae0tX71qJzNODKjn26RKfqpQ
+hahZX5ao8PP0oTePrO2hx7pwm+d7Lzq0I8EfAX3KVEuYPaocnaeqalmSyHItGq/a
+dCqn0UV4ChcYhXMefIGFHOQmTFE=
+-----END CERTIFICATE-----
15 local/certs/www.facebook.com.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQDMvdRHAgT8uGvHP2eloj5MpqY6fkVNBK0sbQgAxH9CRb2bagq0
+IlKT47eK2UHekoTl1HdbTdxVlL8BeSHB5yqoW7ZK02XW3DcwSKjaBRWiLSW8gOB3
+ewyo6aqLGHKJaNrMbpDN4MTlcGNG7unL6jCwMsTUz9FKft55Upizh5v2yQIDAQAB
+AoGAfZC6XKYnatqr9vGy3klHjfjyn5MLa3W/wyxHKTlZlspww5zgXaNjI6IX0cb2
+d8tCSDXq0YtJ4w6rqfqhMmtUo/u/95zOwc5fZWuR2u5sL1PkTiKgVLzNR7+O4GHk
+kVEPTSzQxUQiu7VUsQ66/JZ8nUSNrQBYILnSS9NYc9mXWIUCQQD/hM41AbUL0llS
+7VKUXUR+zqxi02w4KLF6GMwDpN5bcq73AItMku07oUOzaVok3asSyUNEFG4FvrZF
+t8dpp7UDAkEAzSCKz5XDje3muvytV5Ez8VWarmatJ42w+T/z4Ko1iQ5EykgTnm48
+j4e2VWR82Ax02oNwy4GLNT2CNqPfJHzdQwJAR8Eftzr3gI5djzAeFJPsfD/FoSsG
+JO8oh8UW5Z8S4lNeVvbMJ4DLJgevX16idd/Z/riOLzwdeXeI8CMf2MfDyQJAO0zH
+FE/VYrh91Vyqt//wJjp7JyPjtuWNXeERHcfXBjIrzNadZVW1CEFRgso0FTk0pt8M
+wqhGmDOZ6zXgkmnOhQJAaRKXlfpwkNMOtIySPtmy1saj8MSkOfHt4QCDIYAzmWKv
+3rPpUHHtiyCdeRzo23Xqq+G1F433c1JTLD7PYttjrg==
+-----END RSA PRIVATE KEY-----
BIN  local/certutil.exe
Binary file not shown
BIN  local/goagent.exe
Binary file not shown
BIN  local/msvcr90.dll
Binary file not shown
BIN  local/proxy.exe
Binary file not shown
33 local/proxy.ini
@@ -0,0 +1,33 @@
+[listen]
+ip = 127.0.0.1
+port = 8087
+visible = 1
+
+[gae]
+appid = goagentd
+password =
+path = /fetch.py
+debug = INFO
+forcehttps = groups.google.com|code.google.com|mail.google.com|docs.google.com
+certs = facebook.com|www.facebook.com|twitter.com|api.twitter.com|scribe.twitter.com|si0.twimg.com
+#bindhosts = bbs.6park.com|web.6park.com|www.6park.com|club.6park.com
+
+[proxy]
+enable = 0
+type = http
+host = 127.0.0.1
+port = 8080
+username =
+password =
+
+[google]
+prefer = http
+pattern = .appspot.com|.google.com|.googleusercontent.com|.googleapis.com|googlecode.com|.gstatic.com|.google.com.hk
+http = 203.208.39.104|203.208.39.99|203.208.39.22|203.208.39.132|203.208.37.104|203.208.37.99|203.208.37.22|203.208.37.132|203.208.46.99|203.208.46.22|203.208.46.132
+#http = 2404:6800:8005::6a|2404:6800:8005::62|2404:6800:8005::2c
+https = 203.208.46.18|203.208.46.171|203.208.46.17|203.208.46.27|203.208.46.28|203.208.46.65|203.208.46.66|203.208.46.103|203.208.46.100|203.208.46.162|203.208.46.171||74.125.71.17|74.125.71.18|74.125.71.19|74.125.71.32|74.125.71.33|74.125.71.34|74.125.71.35|74.125.71.36|74.125.71.37|74.125.71.38|74.125.71.39|74.125.71.40|74.125.71.41|74.125.71.42|74.125.71.43|74.125.71.44|74.125.71.45|74.125.71.46|74.125.71.47|74.125.71.48|74.125.71.49|74.125.71.50|74.125.71.51|74.125.71.52|74.125.71.53|74.125.71.54|74.125.71.56|74.125.71.57|74.125.71.58|74.125.71.59|74.125.71.60|74.125.71.61|74.125.71.62|74.125.71.63|74.125.71.64|74.125.71.65|74.125.71.66|74.125.71.68|74.125.71.69|74.125.71.72|74.125.71.73|74.125.71.74|74.125.71.75|74.125.71.76|74.125.71.77|74.125.71.78|74.125.71.79|74.125.71.81|74.125.71.82|74.125.71.83|74.125.71.84|74.125.71.85|74.125.71.86|74.125.71.87|74.125.71.91|74.125.71.93|74.125.71.95|74.125.71.96|74.125.71.98|74.125.71.99|74.125.71.100|74.125.71.101|74.125.71.102|74.125.71.103|74.125.71.104|74.125.71.105|74.125.71.106|74.125.71.112|74.125.71.113|74.125.71.115|74.125.71.116|74.125.71.117|74.125.71.118|74.125.71.120|74.125.71.123|74.125.71.125|74.125.71.136|74.125.71.137|74.125.71.138|74.125.71.139|74.125.71.141|74.125.71.142|74.125.71.143|74.125.71.144|74.125.71.145|74.125.71.146|74.125.71.147|74.125.71.148|74.125.71.149|74.125.71.152|74.125.71.154|74.125.71.155|74.125.71.156|74.125.71.157|74.125.71.160|74.125.71.161|74.125.71.162|74.125.71.163|74.125.71.164|74.125.71.165|74.125.71.166|74.125.71.167|74.125.71.176|74.125.71.178|74.125.71.184|74.125.71.189|74.125.71.190|74.125.71.191|74.125.71.193|74.125.71.210|74.125.71.211
+timeout = 4
+sample = 6
+
+[hosts]
+www.253874.com = 76.73.90.170
718 local/proxy.py
@@ -0,0 +1,718 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Based on GAppProxy 2.0.0 by Du XiaoGang <dugang@188.com>
+# Based on WallProxy 0.4.0 by hexieshe <www.ehust@gmail.com>
+
+__version__ = 'beta'
+__author__ = 'phus.lu@gmail.com'
+
+import sys, os, re, time
+import errno, zlib, struct, binascii
+import logging
+import httplib, urllib2, urlparse, socket, select
+import BaseHTTPServer, SocketServer
+import random
+import ConfigParser
+import ssl
+import ctypes
+import threading, Queue
+try:
+ import OpenSSL
+except ImportError:
+ OpenSSL = None
+
+class Common(object):
+ '''global config module, based on GappProxy 2.0.0'''
+ FILENAME = sys.argv[1] if len(sys.argv) == 2 and os.path.isfile(os.sys.argv[1]) else os.path.splitext(__file__)[0] + '.ini'
+ ConfigParser.RawConfigParser.OPTCRE = re.compile(r'(?P<option>[^=\s][^=]*)\s*(?P<vi>[=])\s*(?P<value>.*)$')
+
+ def __init__(self):
+ '''read proxy.ini config'''
+ self.config = ConfigParser.ConfigParser()
+ self.config.read(self.__class__.FILENAME)
+
+ self.LISTEN_IP = self.config.get('listen', 'ip')
+ self.LISTEN_PORT = self.config.getint('listen', 'port')
+ self.LISTEN_VISIBLE = self.config.getint('listen', 'visible')
+
+ self.GAE_APPIDS = self.config.get('gae', 'appid').replace('.appspot.com', '').split('|')
+ self.GAE_PASSWORD = self.config.get('gae', 'password').strip()
+ self.GAE_DEBUG = self.config.get('gae', 'debug')
+ self.GAE_FORCEHTTPS = set(self.config.get('gae', 'forcehttps').split('|'))
+ self.GAE_PATH = self.config.get('gae', 'path')
+ self.GAE_BINDHOSTS = dict((host, self.GAE_APPIDS[0]) for host in self.config.get('gae', 'bindhosts').split('|')) if self.config.has_option('gae', 'bindhosts') else {}
+ self.GAE_CERTS = self.config.get('gae', 'certs').split('|')
+
+ self.PROXY_ENABLE = self.config.getint('proxy', 'enable')
+ self.PROXY_TYPE = self.config.get('proxy', 'type')
+ self.PROXY_HOST = self.config.get('proxy', 'host')
+ self.PROXY_PORT = self.config.getint('proxy', 'port')
+ self.PROXY_USERNAME = self.config.get('proxy', 'username')
+ self.PROXY_PASSWROD = self.config.get('proxy', 'password')
+
+ self.GOOGLE_PREFER = self.config.get('google', 'prefer')
+ self.GOOGLE_PATTEN = tuple(self.config.get('google', 'pattern').split('|'))
+ self.GOOGLE_HTTP = [x.split('|') for x in self.config.get('google', 'http').split('||')]
+ self.GOOGLE_HTTPS = [x.split('|') for x in self.config.get('google', 'https').split('||')]
+ self.GOOGLE_TIMEOUT = self.config.getint('google', 'timeout')
+ self.GOOGLE_SAMPLE = self.config.getint('google', 'sample')
+
+ self.HOSTS = dict((k, [x.split('|') for x in v.split('||')]) for (k, v) in self.config.items('hosts'))
+ logging.basicConfig(level=getattr(logging, self.GAE_DEBUG), format='%(levelname)s - - %(asctime)s %(message)s', datefmt='[%d/%b/%Y %H:%M:%S]')
+
+ def select_appid(self, url):
+ appid = None
+ if len(self.GAE_APPIDS) == 1:
+ return self.GAE_APPIDS[0]
+ if self.GAE_BINDHOSTS:
+ appid = self.GAE_BINDHOSTS.get(urlparse.urlsplit(url)[1])
+ appid = appid or random.choice(self.GAE_APPIDS)
+ return appid
+
+ def info(self):
+ info = ''
+ info += '--------------------------------------------\n'
+ info += 'OpenSSL Module : %s\n' % ('Enabled' if OpenSSL else 'Disabled')
+ info += 'Listen Address : %s:%d\n' % (self.LISTEN_IP, self.LISTEN_PORT)
+ info += 'Log Level : %s\n' % self.GAE_DEBUG
+ info += 'Local Proxy : %s://%s:%s\n' % (self.PROXY_TYPE, self.PROXY_HOST, self.PROXY_PORT) if self.PROXY_ENABLE else ''
+ info += 'GAE Mode : %s\n' % self.GOOGLE_PREFER
+ info += 'GAE APPID : %s\n' % '|'.join(self.GAE_APPIDS)
+ info += 'GAE BindHost : %s\n' % '|'.join('%s=%s' % (k, v) for k, v in self.GAE_BINDHOSTS.items()) if self.GAE_BINDHOSTS else ''
+ info += '--------------------------------------------\n'
+ return info
+
+if __name__ == '__main__':
+ common = Common()
+
+class MultiplexConnection(object):
+ '''multiplex tcp connection class'''
+ def __init__(self, hostslist, port, timeout, sample, proxy=None):
+ self.socket = None
+ self._sockets = set([])
+ self.connect(hostslist, port, timeout, sample)
+ def connect(self, hostslist, port, timeout, sample):
+ for i, hosts in enumerate(hostslist):
+ if len(hosts) > sample:
+ hosts = random.sample(hosts, sample)
+ logging.debug('MultiplexConnection connect (%s, %s)', hosts, port)
+ socs = []
+ for host in hosts:
+ soc_family = socket.AF_INET6 if ':' in host else socket.AF_INET
+ soc = socket.socket(soc_family, socket.SOCK_STREAM)
+ soc.setblocking(0)
+ logging.debug('MultiplexConnection connect_ex (%r, %r)', host, port)
+ err = soc.connect_ex((host, port))
+ self._sockets.add(soc)
+ socs.append(soc)
+ (_, outs, _) = select.select([], socs, [], timeout)
+ if outs:
+ self.socket = outs[0]
+ self.socket.setblocking(1)
+ self._sockets.remove(self.socket)
+ if i > 0:
+ hostslist[:i], hostslist[i:] = hostslist[i:], hostslist[:i]
+ break
+ else:
+ logging.warning('MultiplexConnection Cannot hosts %r:%r', hosts, port)
+ else:
+ raise RuntimeError(r'MultiplexConnection Cannot Connect to hostslist %s:%s' % (hostslist, port))
+ def close(self):
+ for soc in self._sockets:
+ try:
+ soc.close()
+ except:
+ pass
+
+_socket_create_connection = socket.create_connection
+def socket_create_connection(address, timeout=10, source_address=None):
+ host, port = address
+ logging.debug('socket_create_connection connect (%r, %r)', host, port)
+ if host.endswith(common.GOOGLE_PATTEN):
+ hostslist = common.GOOGLE_HTTP if port == 80 else common.GOOGLE_HTTPS
+ else:
+ hostslist = common.HOSTS.get(host)
+ if hostslist is not None:
+ msg = "socket_create_connection returns an empty list"
+ try:
+ hostslist = hostslist or [[x[-1][0] for x in socket.getaddrinfo(host, 80)]]
+ logging.debug("socket_create_connection connect hostslist: (%r, %r)", hostslist, port)
+ conn = MultiplexConnection(hostslist, port, common.GOOGLE_TIMEOUT, common.GOOGLE_SAMPLE)
+ conn.close()
+ soc = conn.socket
+ soc.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)
+ return soc
+ except socket.error, msg:
+ logging.error('socket_create_connection connect fail: (%r, %r)', hostslist, port)
+ soc = None
+ if not soc:
+ raise socket.error, msg
+ else:
+ return _socket_create_connection(address, timeout)
+socket.create_connection = socket_create_connection
+
+_httplib_HTTPConnection_putrequest = httplib.HTTPConnection.putrequest
+def httplib_HTTPConnection_putrequest(self, method, url, skip_host=0, skip_accept_encoding=1):
+ return _httplib_HTTPConnection_putrequest(self, method, url, skip_host, skip_accept_encoding)
+httplib.HTTPConnection.putrequest = httplib_HTTPConnection_putrequest
+
+def socket_forward(local, remote, timeout=60, tick=2, maxping=None, maxpong=None):
+ count = timeout // tick
+ try:
+ while 1:
+ count -= 1
+ (ins, _, errors) = select.select([local, remote], [], [local, remote], tick)
+ if errors:
+ break
+ if ins:
+ for soc in ins:
+ data = soc.recv(8192)
+ if data:
+ if soc is local:
+ remote.send(data)
+ count = maxping or timeout // tick
+ else:
+ local.send(data)
+ count = maxpong or timeout // tick
+ if count == 0:
+ break
+ except Exception, ex:
+ logging.warning('socket_forward error=%s', ex)
+ raise
+
+class RootCA(object):
+ '''RootCA module, based on WallProxy 0.4.0'''
+
+ CA = None
+ CALock = threading.Lock()
+
+ @staticmethod
+ def readFile(filename):
+ try:
+ f = open(filename, 'rb')
+ c = f.read()
+ f.close()
+ return c
+ except IOError:
+ return None
+
+ @staticmethod
+ def writeFile(filename, content):
+ f = open(filename, 'wb')
+ f.write(str(content))
+ f.close()
+
+ @staticmethod
+ def createKeyPair(type=None, bits=1024):
+ if type is None:
+ type = OpenSSL.crypto.TYPE_RSA
+ pkey = OpenSSL.crypto.PKey()
+ pkey.generate_key(type, bits)
+ return pkey
+
+ @staticmethod
+ def createCertRequest(pkey, digest='sha1', **subj):
+ req = OpenSSL.crypto.X509Req()
+ subject = req.get_subject()
+ for k,v in subj.iteritems():
+ setattr(subject, k, v)
+ req.set_pubkey(pkey)
+ req.sign(pkey, digest)
+ return req
+
+ @staticmethod
+ def createCertificate(req, (issuerKey, issuerCert), serial, (notBefore, notAfter), digest='sha1'):
+ cert = OpenSSL.crypto.X509()
+ cert.set_serial_number(serial)
+ cert.gmtime_adj_notBefore(notBefore)
+ cert.gmtime_adj_notAfter(notAfter)
+ cert.set_issuer(issuerCert.get_subject())
+ cert.set_subject(req.get_subject())
+ cert.set_pubkey(req.get_pubkey())
+ cert.sign(issuerKey, digest)
+ return cert
+
+ @staticmethod
+ def loadPEM(pem, type):
+ handlers = ('load_privatekey', 'load_certificate_request', 'load_certificate')
+ return getattr(OpenSSL.crypto, handlers[type])(OpenSSL.crypto.FILETYPE_PEM, pem)
+
+ @staticmethod
+ def dumpPEM(obj, type):
+ handlers = ('dump_privatekey', 'dump_certificate_request', 'dump_certificate')
+ return getattr(OpenSSL.crypto, handlers[type])(OpenSSL.crypto.FILETYPE_PEM, obj)
+
+ @staticmethod
+ def makeCA():
+ pkey = RootCA.createKeyPair(bits=2048)
+ subj = {'countryName': 'CN', 'stateOrProvinceName': 'Internet',
+ 'localityName': 'Cernet', 'organizationName': 'GoAgent',
+ 'organizationalUnitName': 'GoAgent Root', 'commonName': 'GoAgent CA'}
+ req = RootCA.createCertRequest(pkey, **subj)
+ cert = RootCA.createCertificate(req, (pkey, req), 0, (0, 60*60*24*7305)) #20 years
+ return (RootCA.dumpPEM(pkey, 0), RootCA.dumpPEM(cert, 2))
+
+ @staticmethod
+ def makeCert(host, (cakey, cacrt), serial):
+ pkey = RootCA.createKeyPair()
+ subj = {'countryName': 'CN', 'stateOrProvinceName': 'Internet',
+ 'localityName': 'Cernet', 'organizationName': host,
+ 'organizationalUnitName': 'GoAgent Branch', 'commonName': host}
+ req = RootCA.createCertRequest(pkey, **subj)
+ cert = RootCA.createCertificate(req, (cakey, cacrt), serial, (0, 60*60*24*7305))
+ return (RootCA.dumpPEM(pkey, 0), RootCA.dumpPEM(cert, 2))
+
+ @staticmethod
+ def getCertificate(host):
+ basedir = os.path.dirname(__file__)
+ keyFile = os.path.join(basedir, 'certs/%s.key' % host)
+ crtFile = os.path.join(basedir, 'certs/%s.crt' % host)
+ if os.path.exists(keyFile):
+ return (keyFile, crtFile)
+ if not OpenSSL:
+ keyFile = os.path.join(basedir, 'CA.key')
+ crtFile = os.path.join(basedir, 'CA.crt')
+ return (keyFile, crtFile)
+ if not os.path.isfile(keyFile):
+ with RootCA.CALock:
+ if not os.path.isfile(keyFile):
+ logging.info('RootCA getCertificate for %r', host)
+ serialFile = os.path.join(basedir, 'CA.srl')
+ SERIAL = RootCA.readFile(serialFile)
+ SERIAL = int(SERIAL)+1
+ key, crt = RootCA.makeCert(host, RootCA.CA, SERIAL)
+ RootCA.writeFile(keyFile, key)
+ RootCA.writeFile(crtFile, crt)
+ RootCA.writeFile(serialFile, str(SERIAL))
+ return (keyFile, crtFile)
+
+ @staticmethod
+ def checkCA():
+ #Check CA imported
+ if os.name == 'nt':
+ basedir = os.path.dirname(__file__)
+ os.environ['PATH'] += os.pathsep + basedir
+ #cmd = r'certmgr.exe -add "%s\CA.crt" -c -s -r localMachine Root >NUL' % basedir
+ cmd = r'certutil.exe -store Root "GoAgent CA" >NUL || certutil.exe -f -addstore root CA.crt'
+ if os.system(cmd) != 0:
+ logging.warn('Import GoAgent CA \'CA.crt\' %r failed.', cmd)
+ #Check CA file
+ cakeyFile = os.path.join(os.path.dirname(__file__), 'CA.key')
+ cacrtFile = os.path.join(os.path.dirname(__file__), 'CA.crt')
+ cakey = RootCA.readFile(cakeyFile)
+ cacrt = RootCA.readFile(cacrtFile)
+ if OpenSSL:
+ RootCA.CA = (RootCA.loadPEM(cakey, 0), RootCA.loadPEM(cacrt, 2))
+ for host in common.GAE_CERTS:
+ RootCA.getCertificate(host)
+
+def gae_encode_data(dic):
+ return '&'.join('%s=%s' % (k, binascii.b2a_hex(str(v))) for k, v in dic.iteritems())
+
+def gae_decode_data(qs):
+ return dict((k, binascii.a2b_hex(v)) for k, v in (x.split('=') for x in qs.split('&')))
+
+class GaeProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ partSize = 1024000
+ fetchTimeout = 5
+ FR_Headers = ('', 'host', 'vary', 'via', 'x-forwarded-for', 'proxy-authorization', 'proxy-connection', 'upgrade', 'keep-alive')
+ opener = None
+ opener_lock = threading.Lock()
+
+ def address_string(self):
+ return '%s:%s' % self.client_address[:2]
+
+ def send_response(self, code, message=None):
+ self.log_request(code)
+ if message is None:
+ if code in self.responses:
+ message = self.responses[code][0]
+ else:
+ message = 'GoAgent Notify'
+ if self.request_version != 'HTTP/0.9':
+ self.wfile.write('%s %d %s\r\n' % (self.protocol_version, code, message))
+
+ def end_error(self, code, message=None, data=None):
+ if not data:
+ self.send_error(code, message)
+ else:
+ self.send_response(code, message)
+ self.wfile.write(data)
+ self.connection.close()
+
+ def finish(self):
+ try:
+ self.wfile.close()
+ self.rfile.close()
+ except socket.error, (err, _):
+ # Connection closed by browser
+ if err == 10053 or err == errno.EPIPE:
+ self.log_message('socket.error: [%s] "Software caused connection abort"', err)
+ else:
+ raise
+
+ def _opener(self):
+ '''double-checked locking url opener'''
+ if self.opener is None:
+ with GaeProxyHandler.opener_lock:
+ if self.opener is None:
+ if common.PROXY_ENABLE:
+ proxies = {common.PROXY_TYPE:'%s:%d'%(common.PROXY_HOST, common.PROXY_PORT)}
+ proxy_handler = urllib2.ProxyHandler(proxies)
+ else:
+ proxy_handler = urllib2.ProxyHandler({})
+ self.opener = urllib2.build_opener(proxy_handler)
+ self.opener.addheaders = []
+ return self.opener
+
+ def _fetch(self, url, method, headers, payload):
+ errors = []
+ params = {'url':url, 'method':method, 'headers':str(headers), 'payload':payload}
+ logging.debug('GaeProxyHandler fetch params %s', params)
+ if common.GAE_PASSWORD:
+ params['password'] = common.GAE_PASSWORD
+ params = gae_encode_data(params)
+ for i in range(1, 4):
+ try:
+ appid = common.select_appid(url)
+ logging.debug('GaeProxyHandler fetch %r appid=%r', url, appid)
+ if not common.PROXY_ENABLE:
+ fetchserver = '%s://%s.appspot.com%s' % (common.GOOGLE_PREFER, appid, common.GAE_PATH)
+ else:
+ fetchhost = random.choice(common.GOOGLE_HTTP[0] if common.GOOGLE_PREFER == 'http' else common.GOOGLE_HTTPS[0])
+ fetchserver = '%s://%s%s' % (common.GOOGLE_PREFER, fetchhost, common.GAE_PATH)
+ request = urllib2.Request(fetchserver, zlib.compress(params, 9))
+ request.add_header('Content-Type', 'application/octet-stream')
+ if common.PROXY_ENABLE:
+ request.add_header('Host', '%s.appspot.com' % appid)
+ response = self._opener().open(request)
+ data = response.read()
+ response.close()
+ except urllib2.HTTPError, e:
+ # www.google.cn:80 is down, switch to https
+ if e.code == 502 or e.code == 504:
+ common.GOOGLE_PREFER = 'https'
+ sys.stdout.write(common.info())
+ errors.append('%d: %s' % (e.code, httplib.responses.get(e.code, 'Unknown HTTPError')))
+ continue
+ except urllib2.URLError, e:
+ if e.reason[0] in (11004, 10051, 10054, 10060, 'timed out'):
+ # it seems that google.cn is reseted, switch to https
+ if e.reason[0] == 10054:
+ common.GOOGLE_PREFER = 'https'
+ sys.stdout.write(common.info())
+ errors.append(str(e))
+ continue
+ except Exception, e:
+ errors.append(repr(e))
+ continue
+
+ try:
+ if data[0] == '0':
+ raw_data = data[1:]
+ elif data[0] == '1':
+ raw_data = zlib.decompress(data[1:])
+ else:
+ raise ValueError('Data format not match(%s)' % url)
+ data = {}
+ data['code'], hlen, clen = struct.unpack('>3I', raw_data[:12])
+ if len(raw_data) != 12+hlen+clen:
+ raise ValueError('Data length not match')
+ data['content'] = raw_data[12+hlen:]
+ if data['code'] == 555: #Urlfetch Failed
+ raise ValueError(data['content'])
+ data['headers'] = gae_decode_data(raw_data[12:12+hlen])
+ return (0, data)
+ except Exception, e:
+ errors.append(str(e))
+ return (-1, errors)
+
+ def _RangeFetch(self, m, data):
+ m = map(int, m.groups())
+ start = m[0]
+ end = m[2] - 1
+ if 'range' in self.headers:
+ req_range = re.search(r'(\d+)?-(\d+)?', self.headers['range'])
+ if req_range:
+ req_range = [u and int(u) for u in req_range.groups()]
+ if req_range[0] is None:
+ if req_range[1] is not None:
+ if m[1]-m[0]+1==req_range[1] and m[1]+1==m[2]:
+ return False
+ if m[2] >= req_range[1]:
+ start = m[2] - req_range[1]
+ else:
+ start = req_range[0]
+ if req_range[1] is not None:
+ if m[0]==req_range[0] and m[1]==req_range[1]:
+ return False
+ if end > req_range[1]:
+ end = req_range[1]
+ data['headers']['content-range'] = 'bytes %d-%d/%d' % (start, end, m[2])
+ elif start == 0:
+ data['code'] = 200
+ del data['headers']['content-range']
+ data['headers']['content-length'] = end-start+1
+ partSize = self.__class__.partSize
+ self.send_response(data['code'])
+ for k,v in data['headers'].iteritems():
+ self.send_header(k.title(), v)
+ self.end_headers()
+ if start == m[0]:
+ self.wfile.write(data['content'])
+ start = m[1] + 1
+ partSize = len(data['content'])
+ failed = 0
+ logging.info('>>>>>>>>>>>>>>> Range Fetch started')
+ while start <= end:
+ self.headers['Range'] = 'bytes=%d-%d' % (start, start + partSize - 1)
+ retval, data = self._fetch(self.path, self.command, self.headers, '')
+ if retval != 0:
+ time.sleep(4)
+ continue
+ m = re.search(r'bytes\s+(\d+)-(\d+)/(\d+)', data['headers'].get('content-range',''))
+ if not m or int(m.group(1))!=start:
+ if failed >= 1:
+ break
+ failed += 1
+ continue
+ start = int(m.group(2)) + 1
+ logging.info('>>>>>>>>>>>>>>> %s %d' % (data['headers']['content-range'], end))
+ failed = 0
+ self.wfile.write(data['content'])
+ logging.info('>>>>>>>>>>>>>>> Range Fetch ended')
+ self.connection.close()
+ return True
+
+ def do_CONNECT(self):
+ host, _, port = self.path.rpartition(':')
+ if host.endswith(common.GOOGLE_PATTEN) or host in common.HOSTS:
+ return self.do_CONNECT_Direct()
+ else:
+ return self.do_CONNECT_GAE()
+
+ def do_CONNECT_Direct(self):
+ try:
+ logging.debug('GaeProxyHandler.do_CONNECT_Directt %s' % self.path)
+ host, _, port = self.path.rpartition(':')
+ if not common.PROXY_ENABLE:
+ soc = socket.create_connection((host, int(port)))
+ self.log_request(200)
+ self.wfile.write('%s 200 Connection established\r\nProxy-agent: %s\r\n\r\n' % (self.protocol_version, self.version_string()))
+ else:
+ soc = socket.create_connection((common.PROXY_HOST, common.PROXY_PORT))
+ if host.endswith(common.GOOGLE_PATTEN):
+ ip = random.choice(common.GOOGLE_HTTPS[0])
+ else:
+ ip = random.choice(common.HOSTS.get(host, host)[0])
+ data = '%s %s:%s %s\r\n\r\n' % (self.command, ip, port, self.protocol_version)
+ soc.send(data)
+ socket_forward(self.connection, soc, maxping=8)
+ except:
+ logging.exception('GaeProxyHandler.do_CONNECT_Direct Error')
+ self.send_error(502, 'GaeProxyHandler.do_CONNECT_Direct Error')
+ finally:
+ try:
+ self.connection.close()
+ except:
+ pass
+ try:
+ soc.close()
+ except:
+ pass
+
+ def do_CONNECT_GAE(self):
+ # for ssl proxy
+ host, _, port = self.path.rpartition(':')
+ keyFile, crtFile = RootCA.getCertificate(host)
+ self.send_response(200)
+ self.end_headers()
+ try:
+ ssl_sock = ssl.wrap_socket(self.connection, keyFile, crtFile, True)
+ except ssl.SSLError, e:
+ logging.exception('SSLError: %s', e)
+ return
+
+ # rewrite request line, url to abs
+ first_line = ''
+ while True:
+ data = ssl_sock.read()
+ # EOF?
+ if data == '':
+ # bad request
+ ssl_sock.close()
+ self.connection.close()
+ return
+ # newline(\r\n)?
+ first_line += data
+ if '\n' in first_line:
+ first_line, data = first_line.split('\n', 1)
+ first_line = first_line.rstrip('\r')
+ break
+ # got path, rewrite
+ method, path, ver = first_line.split()
+ if path.startswith('/'):
+ path = 'https://%s%s' % (host if port=='443' else self.path, path)
+ # connect to local proxy server
+ localhost = {'0.0.0.0':'127.0.0.1','::':'::1'}.get(common.LISTEN_IP, common.LISTEN_IP)
+ localport = common.LISTEN_PORT
+ sock = socket.socket(LocalProxyServer.address_family, socket.SOCK_STREAM)
+ sock.connect((localhost, localport))
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 32*1024)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 32*1024)
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ sock.send('%s %s %s\r\n%s' % (method, path, ver, data))
+
+ # forward https request
+ ssl_sock.settimeout(1)
+ while True:
+ try:
+ data = ssl_sock.read(8192)
+ except ssl.SSLError, e:
+ if str(e).lower().find('timed out') == -1:
+ # error
+ sock.close()
+ ssl_sock.close()
+ self.connection.close()
+ return
+ # timeout
+ break
+ if data != '':
+ sock.send(data)
+ else:
+ # EOF
+ break
+
+ ssl_sock.setblocking(True)
+ # simply forward response
+ while True:
+ data = sock.recv(8192)
+ if data != '':
+ try:
+ ssl_sock.write(data)
+ except socket.error, (err, _):
+ if err == 10053 or err == errno.EPIPE:
+ self.log_message('socket.error: [%s] Software caused connection abort', err)
+ else:
+ raise
+ else:
+ # EOF
+ break
+ # clean
+ sock.close()
+ ssl_sock.shutdown(socket.SHUT_WR)
+ ssl_sock.close()
+ self.connection.close()
+
+ def do_METHOD(self):
+ host = self.headers.get('host')
+ if host in common.GAE_FORCEHTTPS:
+ self.send_response(301)
+ self.send_header("Location", self.path.replace('http://', 'https://'))
+ self.end_headers()
+ return
+ if host not in common.HOSTS:
+ return self.do_METHOD_GAE()
+ else:
+ return self.do_METHOD_Direct()
+
+ def do_METHOD_Direct(self):
+ scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.path, 'http')
+ try:
+ host, _, port = netloc.rpartition(':')
+ port = int(port)
+ except ValueError:
+ host = netloc
+ port = 80
+ try:
+ self.log_request()
+ if not common.PROXY_ENABLE:
+ soc = socket.create_connection((host, port))
+ data = '%s %s %s\r\n' % (self.command, urlparse.urlunparse(('', '', path, params, query, '')), self.request_version)
+ else:
+ soc = socket.create_connection((common.PROXY_HOST, common.PROXY_PORT))
+ hostslist = common.resolve_host(host)
+ if len(hostslist):
+ data = '%s %s %s\r\n' % (self.command, urlparse.urlunparse((scheme, random.choice(hostslist[0]), path, params, query, '')), self.request_version)
+ data += 'Host: %s\r\n' % host
+ else:
+ data = '%s %s %s\r\n' % (self.command, self.path, self.request_version)
+ data += 'Proxy-Connection: close\r\n'
+ data += ''.join('%s: %s\r\n' % (k, self.headers[k]) for k in self.headers if not k.lower().startswith('proxy-'))
+ data += 'Connection: close\r\n'
+ data += '\r\n'
+ content_length = int(self.headers.get('content-length', 0))
+ if content_length > 0:
+ data += self.rfile.read(content_length)
+ soc.send(data)
+ socket_forward(self.connection, soc, maxping=10)
+ except Exception, ex:
+ logging.exception('SimpleProxyHandler.do_GET Error, %s', ex)
+ self.send_error(502, 'SimpleProxyHandler.do_GET Error (%s)' % ex)
+ finally:
+ try:
+ self.connection.close()
+ except:
+ pass
+ try:
+ soc.close()
+ except:
+ pass
+
+ def do_METHOD_GAE(self):
+ if self.path[0] == '/':
+ host = self.headers['host']
+ if host.endswith(':80'):
+ host = host[:-3]
+ self.path = 'http://%s%s' % (host , self.path)
+
+ payload_len = int(self.headers.get('content-length', 0))
+ if payload_len > 0:
+ payload = self.rfile.read(payload_len)
+ else:
+ payload = ''
+
+ for k in self.__class__.FR_Headers:
+ try:
+ del self.headers[k]
+ except KeyError:
+ pass
+
+ retval, data = self._fetch(self.path, self.command, self.headers, payload)
+ try:
+ if retval == -1:
+ return self.end_error(502, str(data))
+ code = data['code']
+ headers = data['headers']
+ self.log_request(code)
+ if code == 206 and self.command=='GET':
+ m = re.search(r'bytes\s+(\d+)-(\d+)/(\d+)', headers.get('content-range',''))
+ if m and self._RangeFetch(m, data):
+ return
+ if headers.get('connection', '').lower() == 'close':
+ self.close_connection = 1
+ content = '%s %d %s\r\n%s\r\n%s' % (self.protocol_version, code, self.responses.get(code, ('GoAgent Notify', ''))[0], ''.join('%s: %s\r\n' % (k, v) for k, v in headers.iteritems()), data['content'])
+ self.connection.send(content)
+ except socket.error, (err, _):
+ # Connection closed before proxy return
+ if err == errno.EPIPE or err == 10053:
+ return
+ self.connection.close()
+
+ do_GET = do_METHOD
+ do_POST = do_METHOD
+ do_PUT = do_METHOD
+ do_DELETE = do_METHOD
+
+class LocalProxyServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
+ daemon_threads = True
+ allow_reuse_address = True
+
+if __name__ == '__main__':
+ RootCA.checkCA()
+ sys.stdout.write(common.info())
+ if os.name == 'nt' and not common.LISTEN_VISIBLE:
+ ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
+ SocketServer.TCPServer.address_family = {True:socket.AF_INET6, False:socket.AF_INET}[':' in common.LISTEN_IP]
+ httpd = LocalProxyServer((common.LISTEN_IP, common.LISTEN_PORT), GaeProxyHandler)
+ httpd.serve_forever()
16 server/app.yaml
@@ -0,0 +1,16 @@
+application: your_appid
+version: 1
+runtime: python
+api_version: 1
+
+inbound_services:
+- xmpp_message
+
+handlers:
+- url: /_ah/xmpp/message/chat/
+ script: fetch.py
+ secure: optional
+
+- url: /fetch\.py
+ script: fetch.py
+ secure: optional
484 server/appengine_rpc.py
@@ -0,0 +1,484 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+
+
+"""Tool for performing authenticated RPCs against App Engine."""
+
+
+#import google
+
+import cookielib
+import fancy_urllib
+import logging
+import os
+import re
+import socket
+import sys
+import urllib
+import urllib2
+
+#from google.appengine.tools import dev_appserver_login
+
+
+logger = logging.getLogger('google.appengine.tools.appengine_rpc')
+
+def GetPlatformToken(os_module=os, sys_module=sys, platform=sys.platform):
+ """Returns a 'User-agent' token for the host system platform.
+
+ Args:
+ os_module, sys_module, platform: Used for testing.
+
+ Returns:
+ String containing the platform token for the host system.
+ """
+ if hasattr(sys_module, "getwindowsversion"):
+ windows_version = sys_module.getwindowsversion()
+ version_info = ".".join(str(i) for i in windows_version[:4])
+ return platform + "/" + version_info
+ elif hasattr(os_module, "uname"):
+ uname = os_module.uname()
+ return "%s/%s" % (uname[0], uname[2])
+ else:
+ return "unknown"
+
+def HttpRequestToString(req, include_data=True):
+ """Converts a urllib2.Request to a string.
+
+ Args:
+ req: urllib2.Request
+ Returns:
+ Multi-line string representing the request.
+ """
+
+ headers = ""
+ for header in req.header_items():
+ headers += "%s: %s\n" % (header[0], header[1])
+
+ template = ("%(method)s %(selector)s %(type)s/1.1\n"
+ "Host: %(host)s\n"
+ "%(headers)s")
+ if include_data:
+ template = template + "\n%(data)s"
+
+ return template % {
+ 'method' : req.get_method(),
+ 'selector' : req.get_selector(),
+ 'type' : req.get_type().upper(),
+ 'host' : req.get_host(),
+ 'headers': headers,
+ 'data': req.get_data(),
+ }
+
+class ClientLoginError(urllib2.HTTPError):
+ """Raised to indicate there was an error authenticating with ClientLogin."""
+
+ def __init__(self, url, code, msg, headers, args):
+ urllib2.HTTPError.__init__(self, url, code, msg, headers, None)
+ self.args = args
+ self.reason = args["Error"]
+ self.info = args.get("Info")
+
+ def read(self):
+ return '%d %s: %s' % (self.code, self.msg, self.reason)
+
+
+class AbstractRpcServer(object):
+ """Provides a common interface for a simple RPC server."""
+
+ def __init__(self, host, auth_function, user_agent, source,
+ host_override=None, extra_headers=None, save_cookies=False,
+ auth_tries=3, account_type=None, debug_data=True, secure=True,
+ rpc_tries=3):
+ """Creates a new HttpRpcServer.
+
+ Args:
+ host: The host to send requests to.
+ auth_function: A function that takes no arguments and returns an
+ (email, password) tuple when called. Will be called if authentication
+ is required.
+ user_agent: The user-agent string to send to the server. Specify None to
+ omit the user-agent header.
+ source: The source to specify in authentication requests.
+ host_override: The host header to send to the server (defaults to host).
+ extra_headers: A dict of extra headers to append to every request. Values
+ supplied here will override other default headers that are supplied.
+ save_cookies: If True, save the authentication cookies to local disk.
+ If False, use an in-memory cookiejar instead. Subclasses must
+ implement this functionality. Defaults to False.
+ auth_tries: The number of times to attempt auth_function before failing.
+ account_type: One of GOOGLE, HOSTED_OR_GOOGLE, or None for automatic.
+ debug_data: Whether debugging output should include data contents.
+ rpc_tries: The number of rpc retries upon http server error (i.e.
+ Response code >= 500 and < 600) before failing.
+ """
+ if secure:
+ self.scheme = "https"
+ else:
+ self.scheme = "http"
+ self.host = host
+ self.host_override = host_override
+ self.auth_function = auth_function
+ self.source = source
+ self.authenticated = False
+ self.auth_tries = auth_tries
+ self.debug_data = debug_data
+ self.rpc_tries = rpc_tries
+
+
+ self.account_type = account_type
+
+ self.extra_headers = {}
+ if user_agent:
+ self.extra_headers["User-Agent"] = user_agent
+ if extra_headers:
+ self.extra_headers.update(extra_headers)
+
+ self.save_cookies = save_cookies
+
+ self.cookie_jar = cookielib.MozillaCookieJar()
+ self.opener = self._GetOpener()
+ if self.host_override:
+ logger.info("Server: %s; Host: %s", self.host, self.host_override)
+ else:
+ logger.info("Server: %s", self.host)
+
+
+ if ((self.host_override and self.host_override == "localhost") or
+ self.host == "localhost" or self.host.startswith("localhost:")):
+ self._DevAppServerAuthenticate()
+
+ def _GetOpener(self):
+ """Returns an OpenerDirector for making HTTP requests.
+
+ Returns:
+ A urllib2.OpenerDirector object.
+ """
+ raise NotImplemented()
+
+ def _CreateRequest(self, url, data=None):
+ """Creates a new urllib request."""
+ req = fancy_urllib.FancyRequest(url, data=data)
+ if self.host_override:
+ req.add_header("Host", self.host_override)
+ for key, value in self.extra_headers.iteritems():
+ req.add_header(key, value)
+ return req
+
+ def _GetAuthToken(self, email, password):
+ """Uses ClientLogin to authenticate the user, returning an auth token.
+
+ Args:
+ email: The user's email address
+ password: The user's password
+
+ Raises:
+ ClientLoginError: If there was an error authenticating with ClientLogin.
+ HTTPError: If there was some other form of HTTP error.
+
+ Returns:
+ The authentication token returned by ClientLogin.
+ """
+ account_type = self.account_type
+ if not account_type:
+
+ if (self.host.split(':')[0].endswith(".google.com")
+ or (self.host_override
+ and self.host_override.split(':')[0].endswith(".google.com"))):
+
+ account_type = "HOSTED_OR_GOOGLE"
+ else:
+ account_type = "GOOGLE"
+ data = {
+ "Email": email,
+ "Passwd": password,
+ "service": "ah",
+ "source": self.source,
+ "accountType": account_type
+ }
+
+ req = self._CreateRequest(
+ url="https://www.google.com/accounts/ClientLogin",
+ data=urllib.urlencode(data))
+ try:
+ response = self.opener.open(req)
+ response_body = response.read()
+ response_dict = dict(x.split("=")
+ for x in response_body.split("\n") if x)
+ if os.getenv("APPENGINE_RPC_USE_SID", "0") == "1":
+ self.extra_headers["Cookie"] = (
+ 'SID=%s; Path=/;' % response_dict["SID"])
+ return response_dict["Auth"]
+ except urllib2.HTTPError, e:
+ if e.code == 403:
+ body = e.read()
+ response_dict = dict(x.split("=", 1) for x in body.split("\n") if x)
+ raise ClientLoginError(req.get_full_url(), e.code, e.msg,
+ e.headers, response_dict)
+ else:
+ raise
+
+ def _GetAuthCookie(self, auth_token):
+ """Fetches authentication cookies for an authentication token.
+
+ Args:
+ auth_token: The authentication token returned by ClientLogin.
+
+ Raises:
+ HTTPError: If there was an error fetching the authentication cookies.
+ """
+
+ continue_location = "http://localhost/"
+ args = {"continue": continue_location, "auth": auth_token}
+ login_path = os.environ.get("APPCFG_LOGIN_PATH", "/_ah")
+ req = self._CreateRequest("%s://%s%s/login?%s" %
+ (self.scheme, self.host, login_path,
+ urllib.urlencode(args)))
+ try:
+ response = self.opener.open(req)
+ except urllib2.HTTPError, e:
+ response = e
+ if (response.code != 302 or
+ response.info()["location"] != continue_location):
+ raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg,
+ response.headers, response.fp)
+ self.authenticated = True
+
+ def _Authenticate(self):
+ """Authenticates the user.
+
+ The authentication process works as follows:
+ 1) We get a username and password from the user
+ 2) We use ClientLogin to obtain an AUTH token for the user
+ (see http://code.google.com/apis/accounts/AuthForInstalledApps.html).
+ 3) We pass the auth token to /_ah/login on the server to obtain an
+ authentication cookie. If login was successful, it tries to redirect
+ us to the URL we provided.
+
+ If we attempt to access the upload API without first obtaining an
+ authentication cookie, it returns a 401 response and directs us to
+ authenticate ourselves with ClientLogin.
+ """
+ for unused_i in range(self.auth_tries):
+ credentials = self.auth_function()
+ try:
+ auth_token = self._GetAuthToken(credentials[0], credentials[1])
+ if os.getenv("APPENGINE_RPC_USE_SID", "0") == "1":
+ return
+ except ClientLoginError, e:
+ if e.reason == "BadAuthentication":
+ if e.info == "InvalidSecondFactor":
+ print >>sys.stderr, ("Use an application-specific password instead "
+ "of your regular account password.")
+ print >>sys.stderr, ("See http://www.google.com/"
+ "support/accounts/bin/answer.py?answer=185833")
+ else:
+ print >>sys.stderr, "Invalid username or password."
+ continue
+ if e.reason == "CaptchaRequired":
+ print >>sys.stderr, (
+ "Please go to\n"
+ "https://www.google.com/accounts/DisplayUnlockCaptcha\n"
+ "and verify you are a human. Then try again.")
+ break
+ if e.reason == "NotVerified":
+ print >>sys.stderr, "Account not verified."
+ break
+ if e.reason == "TermsNotAgreed":
+ print >>sys.stderr, "User has not agreed to TOS."
+ break
+ if e.reason == "AccountDeleted":
+ print >>sys.stderr, "The user account has been deleted."
+ break
+ if e.reason == "AccountDisabled":
+ print >>sys.stderr, "The user account has been disabled."
+ break
+ if e.reason == "ServiceDisabled":
+ print >>sys.stderr, ("The user's access to the service has been "
+ "disabled.")
+ break
+ if e.reason == "ServiceUnavailable":
+ print >>sys.stderr, "The service is not available; try again later."
+ break
+ raise
+ self._GetAuthCookie(auth_token)
+ return
+
+ def _DevAppServerAuthenticate(self):
+ """Authenticates the user on the dev_appserver."""
+ credentials = self.auth_function()
+ value = dev_appserver_login.CreateCookieData(credentials[0], True)
+ self.extra_headers["Cookie"] = ('dev_appserver_login="%s"; Path=/;' % value)
+
+ def Send(self, request_path, payload="",
+ content_type="application/octet-stream",
+ timeout=None,
+ **kwargs):
+ """Sends an RPC and returns the response.
+
+ Args:
+ request_path: The path to send the request to, eg /api/appversion/create.
+ payload: The body of the request, or None to send an empty request.
+ content_type: The Content-Type header to use.
+ timeout: timeout in seconds; default None i.e. no timeout.
+ (Note: for large requests on OS X, the timeout doesn't work right.)
+ kwargs: Any keyword arguments are converted into query string parameters.
+
+ Returns:
+ The response body, as a string.
+ """
+ old_timeout = socket.getdefaulttimeout()
+ socket.setdefaulttimeout(timeout)
+ try:
+ tries = 0
+ auth_tried = False
+ while True:
+ tries += 1
+ url = "%s://%s%s" % (self.scheme, self.host, request_path)
+ if kwargs:
+ url += "?" + urllib.urlencode(kwargs)
+ req = self._CreateRequest(url=url, data=payload)
+ req.add_header("Content-Type", content_type)
+
+
+
+ req.add_header("X-appcfg-api-version", "1")
+ try:
+ logger.debug('Sending %s request:\n%s',
+ self.scheme.upper(),
+ HttpRequestToString(req, include_data=self.debug_data))
+ f = self.opener.open(req)
+ response = f.read()
+ f.close()
+ return response
+ except urllib2.HTTPError, e:
+ logger.debug("Got http error, this is try #%s", tries)
+ if tries > self.rpc_tries:
+ raise
+ elif e.code == 401:
+
+ if auth_tried:
+ raise
+ auth_tried = True
+ self._Authenticate()
+ elif e.code >= 500 and e.code < 600:
+
+ continue
+ elif e.code == 302:
+
+
+ if auth_tried:
+ raise
+ auth_tried = True
+ loc = e.info()["location"]
+ logger.debug("Got 302 redirect. Location: %s", loc)
+ if loc.startswith("https://www.google.com/accounts/ServiceLogin"):
+ self._Authenticate()
+ elif re.match(r"https://www.google.com/a/[a-z0-9.-]+/ServiceLogin",
+ loc):
+ self.account_type = os.getenv("APPENGINE_RPC_HOSTED_LOGIN_TYPE",
+ "HOSTED")
+ self._Authenticate()
+ elif loc.startswith("http://%s/_ah/login" % (self.host,)):
+ self._DevAppServerAuthenticate()
+ else:
+ raise
+ else:
+ raise
+ finally:
+ socket.setdefaulttimeout(old_timeout)
+
+
+class HttpRpcServer(AbstractRpcServer):
+ """Provides a simplified RPC-style interface for HTTP requests."""
+
+ DEFAULT_COOKIE_FILE_PATH = "~/.appcfg_cookies"
+
+ def __init__(self, *args, **kwargs):
+ self.certpath = os.path.normpath(os.path.join(
+ os.path.dirname(__file__), '..', '..', '..', 'lib', 'cacerts',
+ 'cacerts.txt'))
+ self.cert_file_available = os.path.exists(self.certpath)
+ super(HttpRpcServer, self).__init__(*args, **kwargs)
+
+ def _CreateRequest(self, url, data=None):
+ """Creates a new urllib request."""
+ req = super(HttpRpcServer, self)._CreateRequest(url, data)
+ if self.cert_file_available and fancy_urllib.can_validate_certs():
+ req.set_ssl_info(ca_certs=self.certpath)
+ return req
+
+ def _Authenticate(self):
+ """Save the cookie jar after authentication."""
+ if self.cert_file_available and not fancy_urllib.can_validate_certs():
+
+
+ logger.warn("""ssl module not found.
+Without the ssl module, the identity of the remote host cannot be verified, and
+connections may NOT be secure. To fix this, please install the ssl module from
+http://pypi.python.org/pypi/ssl .
+To learn more, see http://code.google.com/appengine/kb/general.html#rpcssl .""")
+ super(HttpRpcServer, self)._Authenticate()
+ if self.cookie_jar.filename is not None and self.save_cookies:
+ logger.info("Saving authentication cookies to %s",
+ self.cookie_jar.filename)
+ self.cookie_jar.save()
+
+ def _GetOpener(self):
+ """Returns an OpenerDirector that supports cookies and ignores redirects.
+
+ Returns:
+ A urllib2.OpenerDirector object.
+ """
+ opener = urllib2.OpenerDirector()
+ opener.add_handler(fancy_urllib.FancyProxyHandler())
+ opener.add_handler(urllib2.UnknownHandler())
+ opener.add_handler(urllib2.HTTPHandler())
+ opener.add_handler(urllib2.HTTPDefaultErrorHandler())
+ opener.add_handler(fancy_urllib.FancyHTTPSHandler())
+ opener.add_handler(urllib2.HTTPErrorProcessor())
+
+ if self.save_cookies:
+ self.cookie_jar.filename = os.path.expanduser(
+ HttpRpcServer.DEFAULT_COOKIE_FILE_PATH)
+
+ if os.path.exists(self.cookie_jar.filename):
+ try:
+ self.cookie_jar.load()
+ self.authenticated = True
+ logger.info("Loaded authentication cookies from %s",
+ self.cookie_jar.filename)
+ except (OSError, IOError, cookielib.LoadError), e:
+
+ logger.debug("Could not load authentication cookies; %s: %s",
+ e.__class__.__name__, e)
+ self.cookie_jar.filename = None
+ else:
+
+
+ try:
+ fd = os.open(self.cookie_jar.filename, os.O_CREAT, 0600)
+ os.close(fd)
+ except (OSError, IOError), e:
+
+ logger.debug("Could not create authentication cookies file; %s: %s",
+ e.__class__.__name__, e)
+ self.cookie_jar.filename = None
+
+ opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar))
+ return opener
410 server/fancy_urllib.py
@@ -0,0 +1,410 @@
+#!/usr/bin/python2.4
+#
+# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software
+# Foundation; All Rights Reserved
+
+"""A HTTPSConnection/Handler with additional proxy and cert validation features.
+
+In particular, monkey patches in Python r74203 to provide support for CONNECT
+proxies and adds SSL cert validation if the ssl module is present.
+"""
+
+__author__ = "{frew,nick.johnson}@google.com (Fred Wulff and Nick Johnson)"
+
+import base64
+import httplib
+import logging
+import re
+import socket
+import urllib2
+
+from urllib import splittype
+from urllib import splituser
+from urllib import splitpasswd
+
+class InvalidCertificateException(httplib.HTTPException):
+ """Raised when a certificate is provided with an invalid hostname."""
+
+ def __init__(self, host, cert, reason):
+ """Constructor.
+
+ Args:
+ host: The hostname the connection was made to.
+ cert: The SSL certificate (as a dictionary) the host returned.
+ """
+ httplib.HTTPException.__init__(self)
+ self.host = host
+ self.cert = cert
+ self.reason = reason
+
+ def __str__(self):
+ return ('Host %s returned an invalid certificate (%s): %s\n'
+ 'To learn more, see '
+ 'http://code.google.com/appengine/kb/general.html#rpcssl' %
+ (self.host, self.reason, self.cert))
+
+
+try:
+ import ssl
+ _CAN_VALIDATE_CERTS = True
+except ImportError:
+ _CAN_VALIDATE_CERTS = False
+
+
+def can_validate_certs():
+ """Return True if we have the SSL package and can validate certificates."""
+ return _CAN_VALIDATE_CERTS
+
+
+# Reexport SSLError so clients don't have to to do their own checking for ssl's
+# existence.
+if can_validate_certs():
+ SSLError = ssl.SSLError
+else:
+ SSLError = None
+
+
+def create_fancy_connection(tunnel_host=None, key_file=None,
+ cert_file=None, ca_certs=None):
+ # This abomination brought to you by the fact that
+ # the HTTPHandler creates the connection instance in the middle
+ # of do_open so we need to add the tunnel host to the class.
+
+ class PresetProxyHTTPSConnection(httplib.HTTPSConnection):
+ """An HTTPS connection that uses a proxy defined by the enclosing scope."""
+
+ def __init__(self, *args, **kwargs):
+ httplib.HTTPSConnection.__init__(self, *args, **kwargs)
+
+ self._tunnel_host = tunnel_host
+ if tunnel_host:
+ logging.debug("Creating preset proxy https conn: %s", tunnel_host)
+
+ self.key_file = key_file
+ self.cert_file = cert_file
+ self.ca_certs = ca_certs
+ if can_validate_certs():
+ if self.ca_certs:
+ self.cert_reqs = ssl.CERT_REQUIRED
+ else:
+ self.cert_reqs = ssl.CERT_NONE
+
+ def _tunnel(self):
+ self._set_hostport(self._tunnel_host, None)
+ logging.info("Connecting through tunnel to: %s:%d",
+ self.host, self.port)
+ self.send("CONNECT %s:%d HTTP/1.0\r\n\r\n" % (self.host, self.port))
+ response = self.response_class(self.sock, strict=self.strict,
+ method=self._method)
+ (_, code, message) = response._read_status()
+
+ if code != 200:
+ self.close()
+ raise socket.error, "Tunnel connection failed: %d %s" % (
+ code, message.strip())
+
+ while True:
+ line = response.fp.readline()
+ if line == "\r\n":
+ break
+
+ def _get_valid_hosts_for_cert(self, cert):
+ """Returns a list of valid host globs for an SSL certificate.
+
+ Args:
+ cert: A dictionary representing an SSL certificate.
+ Returns:
+ list: A list of valid host globs.
+ """
+ if 'subjectAltName' in cert:
+ return [x[1] for x in cert['subjectAltName'] if x[0].lower() == 'dns']
+ else:
+ # Return a list of commonName fields
+ return [x[0][1] for x in cert['subject']
+ if x[0][0].lower() == 'commonname']
+
+ def _validate_certificate_hostname(self, cert, hostname):
+ """Validates that a given hostname is valid for an SSL certificate.
+
+ Args:
+ cert: A dictionary representing an SSL certificate.
+ hostname: The hostname to test.
+ Returns:
+ bool: Whether or not the hostname is valid for this certificate.
+ """
+ hosts = self._get_valid_hosts_for_cert(cert)
+ for host in hosts:
+ # Convert the glob-style hostname expression (eg, '*.google.com') into a
+ # valid regular expression.
+ host_re = host.replace('.', '\.').replace('*', '[^.]*')
+ if re.search('^%s$' % (host_re,), hostname, re.I):
+ return True
+ return False
+
+
+ def connect(self):
+ # TODO(frew): When we drop support for <2.6 (in the far distant future),
+ # change this to socket.create_connection.
+ self.sock = _create_connection((self.host, self.port))
+
+ if self._tunnel_host:
+ self._tunnel()
+
+ # ssl and FakeSocket got deprecated. Try for the new hotness of wrap_ssl,
+ # with fallback. Note: Since can_validate_certs() just checks for the
+ # ssl module, it's equivalent to attempting to import ssl from
+ # the function, but doesn't require a dynamic import, which doesn't
+ # play nicely with dev_appserver.
+ if can_validate_certs():
+ self.sock = ssl.wrap_socket(self.sock,
+ keyfile=self.key_file,
+ certfile=self.cert_file,
+ ca_certs=self.ca_certs,
+ cert_reqs=self.cert_reqs)
+
+ if self.cert_reqs & ssl.CERT_REQUIRED:
+ cert = self.sock.getpeercert()
+ hostname = self.host.split(':', 0)[0]
+ if not self._validate_certificate_hostname(cert, hostname):
+ raise InvalidCertificateException(hostname, cert,
+ 'hostname mismatch')
+ else:
+ ssl_socket = socket.ssl(self.sock,
+ keyfile=self.key_file,
+ certfile=self.cert_file)
+ self.sock = httplib.FakeSocket(self.sock, ssl_socket)
+
+ return PresetProxyHTTPSConnection
+
+
+# Here to end of _create_connection copied wholesale from Python 2.6"s socket.py
+_GLOBAL_DEFAULT_TIMEOUT = object()
+
+
+def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT):
+ """Connect to *address* and return the socket object.
+
+ Convenience function. Connect to *address* (a 2-tuple ``(host,
+ port)``) and return the socket object. Passing the optional
+ *timeout* parameter will set the timeout on the socket instance
+ before attempting to connect. If no *timeout* is supplied, the
+ global default timeout setting returned by :func:`getdefaulttimeout`
+ is used.
+ """
+
+ msg = "getaddrinfo returns an empty list"
+ host, port = address
+ for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
+ af, socktype, proto, canonname, sa = res
+ sock = None
+ try:
+ sock = socket.socket(af, socktype, proto)
+ if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
+ sock.settimeout(timeout)
+ sock.connect(sa)
+ return sock
+
+ except socket.error, msg:
+ if sock is not None:
+ sock.close()
+
+ raise socket.error, msg
+
+
+class FancyRequest(urllib2.Request):
+ """A request that allows the use of a CONNECT proxy."""
+
+ def __init__(self, *args, **kwargs):
+ urllib2.Request.__init__(self, *args, **kwargs)
+ self._tunnel_host = None
+ self._key_file = None
+ self._cert_file = None
+ self._ca_certs = None
+
+ def set_proxy(self, host, type):
+ saved_type = None
+
+ if self.get_type() == "https" and not self._tunnel_host:
+ self._tunnel_host = self.get_host()
+ saved_type = self.get_type()
+ urllib2.Request.set_proxy(self, host, type)
+
+ if saved_type:
+ # Don't set self.type, we want to preserve the
+ # type for tunneling.
+ self.type = saved_type
+
+ def set_ssl_info(self, key_file=None, cert_file=None, ca_certs=None):
+ self._key_file = key_file
+ self._cert_file = cert_file
+ self._ca_certs = ca_certs
+
+
+class FancyProxyHandler(urllib2.ProxyHandler):
+ """A ProxyHandler that works with CONNECT-enabled proxies."""
+
+ # Taken verbatim from /usr/lib/python2.5/urllib2.py
+ def _parse_proxy(self, proxy):
+ """Return (scheme, user, password, host/port) given a URL or an authority.
+
+ If a URL is supplied, it must have an authority (host:port) component.
+ According to RFC 3986, having an authority component means the URL must
+ have two slashes after the scheme:
+
+ >>> _parse_proxy('file:/ftp.example.com/')
+ Traceback (most recent call last):
+ ValueError: proxy URL with no authority: 'file:/ftp.example.com/'
+
+ The first three items of the returned tuple may be None.
+
+ Examples of authority parsing:
+
+ >>> _parse_proxy('proxy.example.com')
+ (None, None, None, 'proxy.example.com')
+ >>> _parse_proxy('proxy.example.com:3128')
+ (None, None, None, 'proxy.example.com:3128')
+
+ The authority component may optionally include userinfo (assumed to be
+ username:password):
+
+ >>> _parse_proxy('joe:password@proxy.example.com')
+ (None, 'joe', 'password', 'proxy.example.com')
+ >>> _parse_proxy('joe:password@proxy.example.com:3128')
+ (None, 'joe', 'password', 'proxy.example.com:3128')
+
+ Same examples, but with URLs instead:
+
+ >>> _parse_proxy('http://proxy.example.com/')
+ ('http', None, None, 'proxy.example.com')
+ >>> _parse_proxy('http://proxy.example.com:3128/')
+ ('http', None, None, 'proxy.example.com:3128')
+ >>> _parse_proxy('http://joe:password@proxy.example.com/')
+ ('http', 'joe', 'password', 'proxy.example.com')
+ >>> _parse_proxy('http://joe:password@proxy.example.com:3128')
+ ('http', 'joe', 'password', 'proxy.example.com:3128')
+
+ Everything after the authority is ignored:
+
+ >>> _parse_proxy('ftp://joe:password@proxy.example.com/rubbish:3128')
+ ('ftp', 'joe', 'password', 'proxy.example.com')
+
+ Test for no trailing '/' case:
+
+ >>> _parse_proxy('http://joe:password@proxy.example.com')
+ ('http', 'joe', 'password', 'proxy.example.com')
+
+ """
+ scheme, r_scheme = splittype(proxy)
+ if not r_scheme.startswith("/"):
+ # authority
+ scheme = None
+ authority = proxy
+ else:
+ # URL
+ if not r_scheme.startswith("//"):
+ raise ValueError("proxy URL with no authority: %r" % proxy)
+ # We have an authority, so for RFC 3986-compliant URLs (by ss 3.
+ # and 3.3.), path is empty or starts with '/'
+ end = r_scheme.find("/", 2)
+ if end == -1:
+ end = None
+ authority = r_scheme[2:end]
+ userinfo, hostport = splituser(authority)
+ if userinfo is not None:
+ user, password = splitpasswd(userinfo)
+ else:
+ user = password = None
+ return scheme, user, password, hostport
+
+ def proxy_open(self, req, proxy, type):
+ # This block is copied wholesale from Python2.6 urllib2.
+ # It is idempotent, so the superclass method call executes as normal
+ # if invoked.
+ orig_type = req.get_type()
+ proxy_type, user, password, hostport = self._parse_proxy(proxy)
+ if proxy_type is None:
+ proxy_type = orig_type
+ if user and password:
+ user_pass = "%s:%s" % (urllib2.unquote(user), urllib2.unquote(password))
+ creds = base64.b64encode(user_pass).strip()
+ # Later calls overwrite earlier calls for the same header
+ req.add_header("Proxy-authorization", "Basic " + creds)
+ hostport = urllib2.unquote(hostport)
+ req.set_proxy(hostport, proxy_type)
+ # This condition is the change
+ if orig_type == "https":
+ return None
+
+ return urllib2.ProxyHandler.proxy_open(self, req, proxy, type)
+
+
+class FancyHTTPSHandler(urllib2.HTTPSHandler):
+ """An HTTPSHandler that works with CONNECT-enabled proxies."""
+
+ def do_open(self, http_class, req):
+ # Intentionally very specific so as to opt for false negatives
+ # rather than false positives.
+ try:
+ return urllib2.HTTPSHandler.do_open(
+ self,
+ create_fancy_connection(req._tunnel_host,
+ req._key_file,
+ req._cert_file,
+ req._ca_certs),
+ req)
+ except urllib2.URLError, url_error:
+ try:
+ import ssl
+ if (type(url_error.reason) == ssl.SSLError and
+ url_error.reason.args[0] == 1):
+ # Display the reason to the user. Need to use args for python2.5
+ # compat.
+ raise InvalidCertificateException(req.host, '',
+ url_error.reason.args[1])
+ except ImportError:
+ pass
+
+ raise url_error
+
+
+# We have to implement this so that we persist the tunneling behavior
+# through redirects.
+class FancyRedirectHandler(urllib2.HTTPRedirectHandler):
+ """A redirect handler that persists CONNECT-enabled proxy information."""
+
+ def redirect_request(self, req, *args, **kwargs):
+ new_req = urllib2.HTTPRedirectHandler.redirect_request(
+ self, req, *args, **kwargs)
+ # Same thing as in our set_proxy implementation, but in this case
+ # we"ve only got a Request to work with, so it was this or copy
+ # everything over piecemeal.
+ #
+ # Note that we do not persist tunneling behavior from an http request
+ # to an https request, because an http request does not set _tunnel_host.
+ #
+ # Also note that in Python < 2.6, you will get an error in
+ # FancyHTTPSHandler.do_open() on an https urllib2.Request that uses an http
+ # proxy, since the proxy type will be set to http instead of https.
+ # (FancyRequest, and urllib2.Request in Python >= 2.6 set the proxy type to
+ # https.) Such an urllib2.Request could result from this redirect
+ # if you are redirecting from an http request (since an an http request
+ # does not have _tunnel_host set, and thus you will not set the proxy
+ # in the code below), and if you have defined a proxy for https in, say,
+ # FancyProxyHandler, and that proxy has type http.
+ if hasattr(req, "_tunnel_host") and isinstance(new_req, urllib2.Request):
+ if new_req.get_type() == "https":
+ if req._tunnel_host:
+ # req is proxied, so copy the proxy info.
+ new_req._tunnel_host = new_req.get_host()
+ new_req.set_proxy(req.host, "https")
+ else:
+ # req is not proxied, so just make sure _tunnel_host is defined.
+ new_req._tunnel_host = None
+ new_req.type = "https"
+ if hasattr(req, "_key_file") and isinstance(new_req, urllib2.Request):
+ # Copy the auxiliary data in case this or any further redirect is https
+ new_req._key_file = req._key_file
+ new_req._cert_file = req._cert_file
+ new_req._ca_certs = req._ca_certs
+
+ return new_req
220 server/fetch.py
@@ -0,0 +1,220 @@
+#!/usr/bin/env python
+# coding=utf-8
+# Based on GAppProxy by Du XiaoGang <dugang@188.com>
+# Based on WallProxy 0.4.0 by hexieshe <www.ehust@gmail.com>
+
+__version__ = 'beta'
+__author__ = 'phus.lu@gmail.com'
+__password__ = ''
+
+import zlib, logging, time, re, struct, base64
+from google.appengine.ext import webapp
+from google.appengine.ext import db
+from google.appengine.ext.webapp.util import run_wsgi_app
+from google.appengine.api import urlfetch
+from google.appengine.api import xmpp
+from google.appengine.runtime import apiproxy_errors, DeadlineExceededError
+
+def encode_data(dic):
+ return '&'.join('%s=%s' % (k, str(v).encode('hex')) for k, v in dic.iteritems())
+
+def decode_data(qs):
+ return dict((k, v.decode('hex')) for k, v in (x.split('=') for x in qs.split('&')))
+
+class MainHandler(webapp.RequestHandler):
+
+ FRP_Headers = ('', 'x-google-cache-control', 'via')
+ Fetch_Max = 3
+ Fetch_MaxSize = 512*1000
+ Deadline = (15, 30)
+
+ def sendResponse(self, status_code, headers, content='', method='', url=''):
+ self.response.headers['Content-Type'] = 'image/gif' # fake header
+ contentType = headers.get('content-type', '').lower()
+
+ headers = encode_data(headers)
+ # Build send-data
+ rdata = '%s%s%s' % (struct.pack('>3I', status_code, len(headers), len(content)), headers, content)
+ if contentType.startswith(('text', 'application')):
+ data = zlib.compress(rdata)
+ data = '1'+data if len(rdata)>len(data) else '0'+rdata
+ else:
+ data = '0' + rdata
+ if status_code == 555:
+ logging.warning('Response: "%s %s" %s' % (method, url, content))
+ else:
+ logging.debug('Response: "%s %s" %d %d/%d/%d' % (method, url, status_code, len(content), len(rdata), len(data)))
+ return self.response.out.write(data)
+
+ def sendXmppResponse(self, xmpp_message, status_code, headers, content='', method='', url=''):
+ self.response.headers['Content-Type'] = 'application/octet-stream'
+ contentType = headers.get('content-type', '').lower()
+
+ headers = encode_data(headers)
+ rdata = '%s%s%s' % (struct.pack('>3I', status_code, len(headers), len(content)), headers, content)
+ data = '2' + base64.b64encode(rdata)
+ if status_code == 555:
+ logging.warning('Response: "%s %s" %s' % (method, url, content))
+ else:
+ logging.debug('Response: "%s %s" %d %d/%d/%d' % (method, url, status_code, len(content), len(rdata), len(data)))
+ maxsize = 2000
+ for i in xrange(0, len(data), maxsize):
+ xmpp_message.reply(data[i:i+maxsize]+'\r\n')
+ xmpp_message.reply('\r\n')
+
+ def sendNotify(self, status_code, content, method='', url='', fullContent=False):
+ if not fullContent and status_code!=555:
+ content = '<h2>Fetch Server Info</h2><hr noshade="noshade"><p>Code: %d</p>' \
+ '<p>Message: %s</p>' % (status_code, content)
+ headers = {'content-type':'text/html', 'content-length':len(content)}
+ self.sendResponse(status_code, headers, content, method, url)
+
+ def post(self):
+ xmpp_mode = (self.request.path == '/_ah/xmpp/message/chat/')
+ if xmpp_mode:
+ xmpp_message = xmpp.Message(self.request.POST)
+ request = decode_data(xmpp_message.body.replace('&amp;', '&'))
+ logging.debug('MainHandler post get xmpp request %s', request)
+ else:
+ xmpp_message = None
+ request = decode_data(zlib.decompress(self.request.body))
+ #logging.debug('MainHandler post get fetch request %s', request)
+
+ if __password__ and __password__ != request.get('password', ''):
+ return self.sendNotify(403, 'Fobbidon -- wrong password. Please check your proxy.ini and fetch.py.')
+
+ method = request.get('method', 'GET')
+ fetch_method = getattr(urlfetch, method, '')
+ if not fetch_method:
+ return self.sendNotify(555, 'Invalid Method', method)
+
+ url = request.get('url', '')
+ if not url.startswith('http'):
+ return self.sendNotify(555, 'Unsupported Scheme', method, url)
+
+ payload = request.get('payload', '')
+ deadline = MainHandler.Deadline[1 if payload else 0]
+
+ fetch_range = 'bytes=0-%d' % (MainHandler.Fetch_MaxSize - 1)
+ rangeFetch = False
+ headers = {}
+ for line in request.get('headers', '').splitlines():
+ kv = line.split(':', 1)
+ if len(kv) != 2:
+ continue
+ key = kv[0].strip().lower()
+ value = kv[1].strip()
+ #if key in MainHandler.FRS_Headers:
+ # continue
+ if key == 'rangefetch':
+ rangeFetch = True
+ continue
+ if key =='range' and not rangeFetch:
+ m = re.search(r'(\d+)?-(\d+)?', value)
+ if m is None:
+ continue
+ start, end = m.group(1, 2)
+ if not start and not end:
+ continue
+ if not start and int(end) > MainHandler.Fetch_MaxSize:
+ end = '1023'
+ elif not end or int(end)-int(start)+1 > MainHandler.Fetch_MaxSize:
+ end = str(MainHandler.Fetch_MaxSize - 1 + int(start))
+ fetch_range = ('bytes=%s-%s' % (start, end))
+ headers[key] = value
+ headers['Connection'] = 'close'
+
+ for i in range(MainHandler.Fetch_Max):
+ try:
+ response = urlfetch.fetch(url, payload, fetch_method, headers, False, False, deadline)
+ #if method=='GET' and len(response.content)>0x1000000:
+ # raise urlfetch.ResponseTooLargeError(None)
+ break
+ except apiproxy_errors.OverQuotaError, e:
+ time.sleep(4)
+ except DeadlineExceededError:
+ logging.error('DeadlineExceededError(deadline=%s, url=%r)', deadline, url)
+ time.sleep(1)
+ deadline = MainHandler.Deadline[1]
+ except urlfetch.InvalidURLError, e:
+ return self.sendNotify(555, 'Invalid URL: %s' % e, method, url)
+ except urlfetch.ResponseTooLargeError, e:
+ if method == 'GET':
+ deadline = MainHandler.Deadline[1]
+ if not rangeFetch:
+ headers['Range'] = fetch_range
+ else:
+ return self.sendNotify(555, 'Response Too Large: %s' % e, method, url)
+ except Exception, e:
+ if i==0 and method=='GET':
+ deadline = MainHandler.Deadline[1]
+ if not rangeFetch:
+ headers['Range'] = fetch_range
+ else:
+ return self.sendNotify(555, 'Urlfetch error: %s' % e, method, url)
+
+ for k in MainHandler.FRP_Headers:
+ if k in response.headers:
+ del response.headers[k]
+ if 'set-cookie' in response.headers:
+ scs = response.headers['set-cookie'].split(', ')
+ cookies = []
+ i = -1
+ for sc in scs:
+ if re.match(r'[^ =]+ ', sc):
+ try:
+ cookies[i] = '%s, %s' % (cookies[i], sc)
+ except IndexError:
+ pass
+ else:
+ cookies.append(sc)
+ i += 1
+ response.headers['set-cookie'] = '\r\nSet-Cookie: '.join(cookies)
+ response.headers['connection'] = 'close'
+ if xmpp_mode:
+ return self.sendXmppResponse(xmpp_message, response.status_code, response.headers, response.content, method, url)
+ else:
+ return self.sendResponse(response.status_code, response.headers, response.content, method, url)
+
+ def get(self):
+ self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
+ self.response.out.write( \
+'''
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <title>GoAgent %(version)s on GAE/已经在工作了</title>
+ </head>
+ <body>
+ <table width="800" border="0" align="center">
+ <tr><td align="center"><hr></td></tr>
+ <tr><td align="center">
+ <b><h1>GoAgent %(version)s on GAE/已经在工作了</h1></b>
+ </td></tr>
+ <tr><td align="center"><hr></td></tr>
+
+ <tr><td align="center">
+ GoAgent是一个开源的HTTP Proxy软件,使用Python编写,运行于Google App Engine平台上.
+ </td></tr>
+ <tr><td align="center"><hr></td></tr>
+
+ <tr><td align="center">
+ 更多相关介绍,请参考<a href="https://github.com/phus/goagent">GoAgent项目主页</a>.
+ </td></tr>
+ <tr><td align="center"><hr></td></tr>
+
+ <tr><td align="center">
+ <img src="http://code.google.com/appengine/images/appengine-silver-120x30.gif" alt="Powered by Google App Engine" />
+ </td></tr>
+ <tr><td align="center"><hr></td></tr>
+ </table>
+ </body>
+</html>
+''' % dict(version=__version__))
+
+def main():
+ application = webapp.WSGIApplication([('/_ah/xmpp/message/chat/', MainHandler),('/fetch.py', MainHandler)],debug=True)
+ run_wsgi_app(application)
+
+if __name__ == '__main__':
+ main()
3  server/uploader.bat
@@ -0,0 +1,3 @@
+@set PYTHONDONTWRITEBYTECODE=x
+@set PYTHONSCRIPT=%~dp0uploader.py
+@"%~dp0..\local\proxy.exe" || pause
642 server/uploader.py
@@ -0,0 +1,642 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import hashlib
+import httplib
+import urllib
+import urllib2
+import sys
+import os
+import re
+import time
+import getpass
+import appengine_rpc
+import fancy_urllib
+import socket
+import random
+import logging
+import select
+import logging
+
+#logging.basicConfig(level=logging.DEBUG, format='%(levelname)s - - %(asctime)s %(message)s', datefmt='[%d/%b/%Y %H:%M:%S]')
+
+GOOGLE_IP_LIST = '''
+203.208.46.18
+203.208.46.171
+203.208.46.17
+203.208.46.27
+203.208.46.28
+203.208.46.65
+203.208.46.66
+203.208.46.103
+203.208.46.100
+203.208.46.162
+203.208.46.171
+203.208.37.97
+203.208.39.97
+74.125.71.17
+74.125.71.18
+74.125.71.19
+74.125.71.32
+74.125.71.33
+74.125.71.34
+74.125.71.35
+74.125.71.36
+74.125.71.37
+74.125.71.38
+74.125.71.39
+74.125.71.40
+74.125.71.41
+74.125.71.42
+74.125.71.43
+74.125.71.44