Compare commits
1597 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c69af4418 | |||
| 416f696863 | |||
| 789c1cc816 | |||
| 58736dd254 | |||
| a057138880 | |||
| 76087f1749 | |||
| 83935f3a03 | |||
| b93c10ad47 | |||
| 3309137b80 | |||
| 88c4737ba4 | |||
| e5db9b1ccc | |||
| 6e2e622a2f | |||
| 3a66063938 | |||
| 120ddbbcbb | |||
| 39b31abef8 | |||
| ebeca394c7 | |||
| 2206cb3f12 | |||
| cfd07cf893 | |||
| 2e2648fcd5 | |||
| 3070912416 | |||
| 51722eb1a4 | |||
| 5950eff083 | |||
| 5c67cc2e76 | |||
| 01db488caa | |||
| 6cbef1d786 | |||
| dd9a819ea2 | |||
| 401e56224b | |||
| 1ee52f0f55 | |||
| 9efaf9184c | |||
| a8f270405f | |||
| 38606888fe | |||
| 1b22c32ef9 | |||
| 7a1c7e8743 | |||
| 9449177553 | |||
| bbcedc655a | |||
| 40c97ab19e | |||
| 50dd046b82 | |||
| 7d13c99710 | |||
| 6d7c21b2c9 | |||
| f7434109be | |||
| 414d74d06a | |||
| 110cdbf3ae | |||
| ec4ceb4552 | |||
| ef62704030 | |||
| eaba6b6363 | |||
| e1723fc24b | |||
| 2073513d5e | |||
| 36f7d9672f | |||
| ef183e0758 | |||
| 0d2a803711 | |||
| 06b5276981 | |||
| b2d61da41f | |||
| e51c81fc03 | |||
| 26897f06c4 | |||
| 5ca9a7db37 | |||
| b34f5d072f | |||
| eeb514cc81 | |||
| 650ad49ab0 | |||
| 0e5715c4e3 | |||
| b0f1c3d4c5 | |||
| ba935a6cce | |||
| 1370ff78c5 | |||
| 109c15410a | |||
| 3210709810 | |||
| 8fd988d7c5 | |||
| bf89d548d3 | |||
| 51229cbb68 | |||
| 36c5c37dac | |||
| 5a434fafbc | |||
| ea1c2534df | |||
| 1cafbfcaaa | |||
| 2d44ccaee0 | |||
| 96517b7fb1 | |||
| bc381407a7 | |||
| ddc5e775b9 | |||
| ea26188dc0 | |||
| 159e1cee7d | |||
| 4394ad0e9b | |||
| 856bdd1321 | |||
| ff288145df | |||
| 83bbdbd63e | |||
| fa430ee0fb | |||
| 0303ba38e8 | |||
| 2a78b5c144 | |||
| a00b3cdb92 | |||
| 8d3e04679f | |||
| 21ff7b4b97 | |||
| 4ea161f7ad | |||
| dc584ea29b | |||
| 4a01c46aed | |||
| e8d9534b9c | |||
| 96904b160f | |||
| b535be72f8 | |||
| 40f2d8b30f | |||
| 95a1acec0d | |||
| 5ff074cc49 | |||
| 4f0660bb8c | |||
| 708184439e | |||
| b8a33b9618 | |||
| 1c385d5c9b | |||
| 96773f3225 | |||
| 0f320dbd80 | |||
| 6cb233473a | |||
| 1ac4e70115 | |||
| 07f93d276b | |||
| d29571fb01 | |||
| d6000d025e | |||
| 09ef3b20db | |||
| 405331d59b | |||
| eff7df2136 | |||
| 5823e3a99f | |||
| 26d866bbbd | |||
| d3f7be059d | |||
| b52706a3ca | |||
| aebe7baed0 | |||
| ef31e2917c | |||
| 9eea26459a | |||
| 5747b85543 | |||
| ff78a23084 | |||
| 2a95e1ab41 | |||
| ab76cab533 | |||
| dda2a5d01a | |||
| c2afb42fd4 | |||
| 1d53044803 | |||
| d3f8297eb4 | |||
| b02203e3d3 | |||
| 5c7e4e04f9 | |||
| d7dadd7578 | |||
| ab9a758d63 | |||
| cb0935be96 | |||
| 441b388f62 | |||
| cdbcd30d15 | |||
| acc7ca8d4a | |||
| 42e1dd4c41 | |||
| 4cbd3ca832 | |||
| de0b6c0737 | |||
| 1c344211d1 | |||
| c11a87c16a | |||
| 3bf4282037 | |||
| 0c212fbef4 | |||
| 48d1ca1e72 | |||
| 52addb2582 | |||
| 742d9eeef3 | |||
| 55a9d4973c | |||
| 8402657108 | |||
| 8a6f96f9f2 | |||
| 56c53e9188 | |||
| bb67d95669 | |||
| 50acc0dcfb | |||
| e9c73c2d0d | |||
| 07c03c6920 | |||
| f4958b9b53 | |||
| 76f2e7fdb9 | |||
| c0992e8801 | |||
| cf3abaa96f | |||
| e422b28bc3 | |||
| a1a5ffba5d | |||
| f8b86a76dd | |||
| ab1281ceee | |||
| 0ab0f2f4ff | |||
| 09d87023f1 | |||
| 139ad75394 | |||
| c8cf90abfe | |||
| 5d4f8f7d40 | |||
| ea26dc0e97 | |||
| 8d346ea511 | |||
| 44df3cfd4a | |||
| 683458e264 | |||
| 36651698cb | |||
| 0c7e17701f | |||
| 86cd2437aa | |||
| 53f5f9aa43 | |||
| c849762445 | |||
| 32f2c72575 | |||
| 958e1280d7 | |||
| df09d6d221 | |||
| e0875dc928 | |||
| b3a5270bdc | |||
| f617a44d28 | |||
| 75ed3ca660 | |||
| 69f3029430 | |||
| 1203709ab9 | |||
| 15c18189d3 | |||
| a9e95f618b | |||
| 272f9cf59b | |||
| 6e86c95640 | |||
| 81afc5fb1f | |||
| 53ea5e9adc | |||
| 6f420f9098 | |||
| 65846ff40f | |||
| 43f7a989be | |||
| 452d3068f0 | |||
| 69190daf3f | |||
| f57a40677e | |||
| 2d6f42e0b5 | |||
| bccf31501d | |||
| 9b546b5412 | |||
| f48a60d58c | |||
| 0a51c7a6b0 | |||
| 7355c7dfd6 | |||
| bb5a91ee6d | |||
| ca5f7ce9f6 | |||
| ad31e6a9c5 | |||
| 9ef7d133c0 | |||
| 83b842b19d | |||
| df02e39fe1 | |||
| a35c8424a3 | |||
| 5d207810bd | |||
| 6c9d96d5e1 | |||
| 0fc41d1966 | |||
| dd5e745e37 | |||
| c8f0d7f32a | |||
| bd986901c3 | |||
| cdc19492ee | |||
| 635b2a4891 | |||
| e5bac33a04 | |||
| 7b96a07cf5 | |||
| 87e79fdcba | |||
| 03c3404044 | |||
| fa794a982b | |||
| cab32d5d5a | |||
| 8e5a892c45 | |||
| 50dc5c4085 | |||
| 3b58078595 | |||
| 7689139cb3 | |||
| 6269b1ab88 | |||
| 79524185a8 | |||
| 635b81314a | |||
| 4c76e35a2d | |||
| b0ac20425e | |||
| 21a56a0725 | |||
| c57b52ef23 | |||
| 44e103edd5 | |||
| 970de4c205 | |||
| a189c35899 | |||
| c9323a40c8 | |||
| 7a192d50db | |||
| a5a9bd762d | |||
| dd7db00f74 | |||
| a6c20f698c | |||
| f7252ed40e | |||
| 66c30716ea | |||
| 06d7fdf26f | |||
| 8aee732fe8 | |||
| 47233752f5 | |||
| cb6e51531b | |||
| 0b9b886039 | |||
| 1fa0d77b10 | |||
| efbe84964f | |||
| aa77a67a1c | |||
| 78f7cbdc79 | |||
| a731237701 | |||
| f557666b4d | |||
| 5f89e85139 | |||
| 9fb922d18f | |||
| 884c6ed932 | |||
| db77bd4983 | |||
| 85bfcf7158 | |||
| 09a5f8ac0f | |||
| 81f81a63e8 | |||
| e724fafe2f | |||
| cba73997cd | |||
| 29f44fc312 | |||
| 41c125f65e | |||
| 0a555bf767 | |||
| 24331f9715 | |||
| a5500629e5 | |||
| a46533dcf2 | |||
| 12183fbf05 | |||
| c90248920a | |||
| ccb9b7f81c | |||
| 78c0651661 | |||
| d72980e443 | |||
| b24937b666 | |||
| 5ca9ec6674 | |||
| 4ee6da4baa | |||
| e8ce8942be | |||
| 9d0d3708af | |||
| 4c908aac7c | |||
| 6eb1878f66 | |||
| 826dc2e5c3 | |||
| e6ab874308 | |||
| 20b188368a | |||
| ded4f370dc | |||
| 519a6ecdb7 | |||
| c35344d6f1 | |||
| a9865976a3 | |||
| 9a96588afb | |||
| 1f2c573803 | |||
| cc33423c1f | |||
| 1b95c290f1 | |||
| 9b88778c43 | |||
| ae4705ba70 | |||
| 243ddf47ab | |||
| 80d729e3e5 | |||
| 3d64c5f894 | |||
| c4bcc38c53 | |||
| 2c2f816f3a | |||
| 86e115b2f3 | |||
| 1a2783a63b | |||
| cbbab71f5c | |||
| 80add80be2 | |||
| 84adbbc461 | |||
| 75811d22e8 | |||
| e26c7683d2 | |||
| f0e2688a8e | |||
| 06639ff6cd | |||
| 716de7f45a | |||
| 750de0cd31 | |||
| 823ca4d207 | |||
| a187747c7c | |||
| 11ebb16933 | |||
| 0048767022 | |||
| c4b75c6f34 | |||
| 32448063dc | |||
| 86bde91958 | |||
| 90c34406ba | |||
| d7b71aceda | |||
| 25a787529b | |||
| f82965b825 | |||
| f1cf4ee194 | |||
| 5136919c36 | |||
| 334a256638 | |||
| da528f2d9b | |||
| 1b0f930471 | |||
| 09c523e2d2 | |||
| 0b35f41ffd | |||
| 7be1a8ae8a | |||
| bdbf1bdd76 | |||
| 776976a8a2 | |||
| 040ddadb7a | |||
| 11f6f84dd6 | |||
| 82efa16d65 | |||
| 110286b81c | |||
| bc66841cdc | |||
| 8d028966c7 | |||
| d120bbeffc | |||
| eda49483e2 | |||
| 3ef526333a | |||
| f454f1a74f | |||
| e97a4d8847 | |||
| cb8174dbfd | |||
| cfca429067 | |||
| 1ffb9089ba | |||
| c0fc23d7cd | |||
| 4f8ecd598f | |||
| 65365281eb | |||
| c05dfb36d3 | |||
| 42178806d1 | |||
| e34050282e | |||
| b2830b39e0 | |||
| 7997ad2b93 | |||
| efb6ba0f1b | |||
| 80194ad797 | |||
| cda6b2a728 | |||
| 44bde86fde | |||
| 6cb52cacc9 | |||
| 8248491833 | |||
| fbdede28f0 | |||
| 66bc911652 | |||
| c860741ffa | |||
| 80d743afec | |||
| ac9857a965 | |||
| 0e9fd46a5c | |||
| 305d180a5f | |||
| c4f80103b6 | |||
| 0d57e3645a | |||
| 0afdc31f96 | |||
| 91de6e001e | |||
| 908ed3e723 | |||
| 7411073c08 | |||
| 7d838375bb | |||
| f545f30ec0 | |||
| 6579cdfc7f | |||
| 40c48ba804 | |||
| 1c2cb4f439 | |||
| 650dac37a7 | |||
| 55275b23ee | |||
| bce69e1a1b | |||
| f1917ad0de | |||
| 300d243331 | |||
| 6ea6d54af6 | |||
| c1b486a7eb | |||
| 454c9e1534 | |||
| eaa673c4e4 | |||
| 9c389e3007 | |||
| 7e9a5934c5 | |||
| cc17366c1c | |||
| 62f6db35db | |||
| be422001e8 | |||
| b2eb35592f | |||
| a3b26431ce | |||
| f460323cc5 | |||
| 26694d3bd8 | |||
| e96713a998 | |||
| 6359b8639e | |||
| 6c9d5ccd4a | |||
| 234554b459 | |||
| 6df5a82364 | |||
| ac75410657 | |||
| 238929c3ec | |||
| 1f79e3b0a7 | |||
| f5af2afce5 | |||
| 9482bea8af | |||
| a55572e5b3 | |||
| 098eb7cb7a | |||
| 68334e3bb8 | |||
| 124231c3c7 | |||
| f591af2cbd | |||
| ff11d20d9c | |||
| 72911235c5 | |||
| 60de00c73f | |||
| 4e080b59d3 | |||
| bac4b90c1d | |||
| ea47c9aa1c | |||
| a91d9762db | |||
| 6fb11d69f9 | |||
| 1eab3296d1 | |||
| 4352154b84 | |||
| 03cf601921 | |||
| 9f13301613 | |||
| 650158ea8a | |||
| 552fc2700f | |||
| 582afa1451 | |||
| c43739a7ef | |||
| 720f662afe | |||
| e9488d12ee | |||
| 9a87b155ba | |||
| f6a1cd9b64 | |||
| 7b7c9093ce | |||
| fa4c0ec823 | |||
| 08af1da966 | |||
| ed8475dacf | |||
| e91cdca6f3 | |||
| c267168cb7 | |||
| 58b45d8458 | |||
| b77512dfd9 | |||
| bdc6542970 | |||
| c942a44f6a | |||
| 55081fa59b | |||
| cc1d0e803b | |||
| b7a2371220 | |||
| ae65385c38 | |||
| ab70e85f1c | |||
| ff57eb2b43 | |||
| e78cb8089b | |||
| 09eef64514 | |||
| a2c2710760 | |||
| c4abb14ae6 | |||
| 38a0cdb4ab | |||
| c587dfc0dc | |||
| 7a090ffcc9 | |||
| 6ab49367ba | |||
| a06fd31f49 | |||
| 016319784e | |||
| 12f9fb03c3 | |||
| 419a4427d7 | |||
| e434a4fd0c | |||
| d68b163c20 | |||
| 71a559ee4c | |||
| ac00ef1b64 | |||
| 1e9a77c7b2 | |||
| 4aaa555c9b | |||
| a8dd52800e | |||
| fab063f194 | |||
| 5c606aee73 | |||
| 51315d5d2b | |||
| cde9c19f71 | |||
| 4902898880 | |||
| c46b3245b8 | |||
| 3afd94c61d | |||
| ade2fd9403 | |||
| 89632b7acd | |||
| b8cf193911 | |||
| fb4d34ef5b | |||
| 7f7e360cd7 | |||
| 802f7dbc67 | |||
| fc06665d2b | |||
| c4dc829e6d | |||
| bfaf9765ae | |||
| a702e19dff | |||
| 0eab1c0c2b | |||
| 11f55b59a9 | |||
| 172b59c756 | |||
| 8564d3a483 | |||
| 1d595b933a | |||
| 39cd165bd1 | |||
| 0cef181432 | |||
| 262c4c5d95 | |||
| e343d1f1af | |||
| 84a771d9fe | |||
| f6741a9b58 | |||
| bc5de2b884 | |||
| 7d54e6907d | |||
| 4b52d998db | |||
| aa72fd641d | |||
| ebe45d5abe | |||
| b6eb5a1b13 | |||
| 9c25f56fe6 | |||
| bb99695e68 | |||
| 35f0e081a5 | |||
| 40dc17aea5 | |||
| 5fee2f707b | |||
| d6304b087a | |||
| 21f833ea11 | |||
| 900caec09e | |||
| 9fc9f5ad9f | |||
| d8ccc6c05d | |||
| 4e3ad4f7fa | |||
| 8c958cdc2f | |||
| afef730870 | |||
| 0ef9c9c9c9 | |||
| f691795ca1 | |||
| 73bb0ed03e | |||
| faf3780eee | |||
| 98031d296e | |||
| d08b3fcca4 | |||
| 6adb440b84 | |||
| a3e07428b5 | |||
| dfd85f7ed3 | |||
| 4b5edd62d0 | |||
| fb4a0e77af | |||
| edac2419f9 | |||
| 6ba8052a1e | |||
| 51288791c0 | |||
| 9798cdec5c | |||
| 9ae899f420 | |||
| 51aafe3266 | |||
| 8fd09547fa | |||
| aba94a9353 | |||
| e6dfb814fd | |||
| bfa53b1619 | |||
| b9c54e9276 | |||
| 71d7deb3b1 | |||
| e606d98664 | |||
| 36342299c7 | |||
| a05f93debd | |||
| c438704648 | |||
| 0417e495ae | |||
| bda158d6c6 | |||
| ad02c71ad6 | |||
| eee2c73a61 | |||
| 5630b7d2e6 | |||
| 2ee4893325 | |||
| 01aa19edff | |||
| a0db1645f2 | |||
| 324593596a | |||
| b51d85e768 | |||
| bd47303074 | |||
| fdae8cb729 | |||
| 333daa05c5 | |||
| 6a6ead8e6d | |||
| 543c35041d | |||
| 50709acc5f | |||
| 994cec196c | |||
| 910060a14c | |||
| 38b13d710c | |||
| 06f710a9b1 | |||
| fbbd0245de | |||
| 7002806999 | |||
| c49b42060e | |||
| 21b20ac420 | |||
| f2a8990972 | |||
| 8db30a89c0 | |||
| 449b580056 | |||
| 38397accf5 | |||
| 5d49cf2c09 | |||
| d8fa2fb3e3 | |||
| 2f7f898cee | |||
| ae7621ed6a | |||
| f3fb10fb2d | |||
| 9241e5317f | |||
| 3ebd2f53f4 | |||
| 59a944a06c | |||
| f9a0c35daa | |||
| e9629aca47 | |||
| 8d53ee855b | |||
| ec0db47f32 | |||
| 7383d65cb2 | |||
| 3ef3ab72ed | |||
| 00adb8bc22 | |||
| d3fc9a50f6 | |||
| 4adc6d60b9 | |||
| d88bee68c6 | |||
| 67b5e7f96a | |||
| b250d49af8 | |||
| 0f621d0aad | |||
| 6ddaa94bc0 | |||
| fed503501d | |||
| ce5a559926 | |||
| 3b297fa37b | |||
| 95741c6d63 | |||
| ad4e853a8a | |||
| f4631c4bc9 | |||
| c000ee8a3c | |||
| 3ddd88e127 | |||
| 54b209f9e1 | |||
| 4e7a669260 | |||
| 8093bbf5f6 | |||
| 7bb925b6d7 | |||
| d6760d6f50 | |||
| 2191dc70dc | |||
| ff44a3d6df | |||
| b0c3faa228 | |||
| 3928ed08f6 | |||
| c7ae239350 | |||
| 7d51e9123d | |||
| 6fcea2ad83 | |||
| c1918e2b1b | |||
| 098f294cac | |||
| a6f5cc870c | |||
| 60a779c653 | |||
| a8cb0012e1 | |||
| c84919faae | |||
| 3735d4b327 | |||
| 7330406752 | |||
| 1323229362 | |||
| fd100eecc2 | |||
| a11559fe58 | |||
| 7d8e71c9ea | |||
| 8b80938e49 | |||
| 0a53dc1da7 | |||
| 9bbeabcf50 | |||
| de5fd07a22 | |||
| ec92c918cd | |||
| 9f59e61b14 | |||
| 86fd7961a1 | |||
| 9756a7b51f | |||
| aa957f3314 | |||
| 7124e7c9a0 | |||
| 421da99cd8 | |||
| ef1940d227 | |||
| 579e996d3a | |||
| 7ba523393c | |||
| 0a0bcd7b90 | |||
| f1fb525dc8 | |||
| ee87e60239 | |||
| 4adc05d354 | |||
| 86fbf4415a | |||
| babb4412ae | |||
| 74203e07a4 | |||
| 2166224e91 | |||
| 60df01eece | |||
| 58df3312c1 | |||
| f1989193c0 | |||
| 4e7acd9091 | |||
| 4c94606e20 | |||
| 3ca5d0af71 | |||
| 9425e091d8 | |||
| ee5e87fe85 | |||
| dbd156a272 | |||
| bb770f3871 | |||
| b1ad0ab6dc | |||
| 03c3e95b34 | |||
| b63b56960e | |||
| a8250ff65e | |||
| 2d6e0c66a5 | |||
| d8bf2cc25e | |||
| ed05823c78 | |||
| ec351330f1 | |||
| 4f49c87bc6 | |||
| 02ca6428b5 | |||
| b3d0dfc60b | |||
| 622b76b57b | |||
| dd7c81ca4b | |||
| f71a89c265 | |||
| e1dff67c10 | |||
| 31de358bfd | |||
| 212eac0925 | |||
| e36c2b3d6e | |||
| 8b33d56b59 | |||
| 48274ee178 | |||
| 4273405393 | |||
| 3a85de2f3c | |||
| a3aafabde3 | |||
| 30c1c14505 | |||
| 8164c41728 | |||
| 1820af5021 | |||
| b57ca1506d | |||
| 7eaf1655b2 | |||
| b4ea667858 | |||
| 8f45fe823a | |||
| f627151d04 | |||
| 7c232b1331 | |||
| 7be46a4740 | |||
| b57c7abe92 | |||
| c86c428718 | |||
| ed6e17a0ab | |||
| c3454360fc | |||
| 182dab18a6 | |||
| 13c8a98389 | |||
| 05a2c9d254 | |||
| d926dd3806 | |||
| 7cc2f3361d | |||
| c496d6c71c | |||
| 7fc907a874 | |||
| b953468af2 | |||
| 9f4caa4948 | |||
| 86630ce137 | |||
| 2c9477d65c | |||
| 34c002ff68 | |||
| f03688ba72 | |||
| 8c0bb22de3 | |||
| 53c2cbcaee | |||
| 07339aff21 | |||
| 3ca56cfab3 | |||
| 59cf5e890b | |||
| 70950e0048 | |||
| 2781f06549 | |||
| febc994cec | |||
| 21ffde316d | |||
| 2aa4e7c9da | |||
| 9058544f5c | |||
| 227bbf1c03 | |||
| 667998c207 | |||
| 1d426e621c | |||
| 6e4dcdb93b | |||
| 20f35edc83 | |||
| 26cf684fb8 | |||
| 2a4cb6a916 | |||
| caa4a5cbdb | |||
| 91900a7942 | |||
| 04a7a81e27 | |||
| 53d5619c51 | |||
| f793b71569 | |||
| 8aec11a634 | |||
| 30651a531b | |||
| 91ab77dce9 | |||
| 69aa784d32 | |||
| 825031e052 | |||
| ee4a8939d5 | |||
| 89117bbd59 | |||
| 9e4310712c | |||
| 0b35b275d3 | |||
| d6acb0fb19 | |||
| 2db5a04e7a | |||
| d7bfee2414 | |||
| 4a6460f3ed | |||
| 3b1c3b9d44 | |||
| 60256fd076 | |||
| c6f1f159f3 | |||
| 82af4e01bc | |||
| 9ad5f74409 | |||
| 10cf153678 | |||
| 2b09796d1c | |||
| 5ba07db7e3 | |||
| ad0d4ebd36 | |||
| 9f3c14ab1e | |||
| 74cf5d422b | |||
| dcf694588c | |||
| a2b9fc3dee | |||
| 761c16d8cd | |||
| 810705ba01 | |||
| c15917aba4 | |||
| 51cbb91513 | |||
| f8bfbaf361 | |||
| 3e878058e7 | |||
| 9c1b9e8df2 | |||
| 065dcd4d47 | |||
| fb44de4f18 | |||
| 00256fafe8 | |||
| 671db7c516 | |||
| e9f20aee7a | |||
| 8534da98ea | |||
| 265af2d299 | |||
| 82c388a0dd | |||
| 89112baf96 | |||
| 5007d451c2 | |||
| 4775fb4b22 | |||
| 94ed09b437 | |||
| 57962e5757 | |||
| 8a5c8eaf6e | |||
| a75a72a2b9 | |||
| 038eb6d243 | |||
| 2bd8f6938a | |||
| 889f3286b5 | |||
| c821d02f67 | |||
| 9dfdd07f7a | |||
| 0b796f4401 | |||
| a741ffb595 | |||
| cf8284a489 | |||
| ec2a4f9111 | |||
| 6a9f6a173a | |||
| 54c013012e | |||
| cac0cf35f6 | |||
| 30029f489e | |||
| 968a01053f | |||
| 2faeebe9e7 | |||
| f6727a56d2 | |||
| 4c24c004db | |||
| 571133f2ff | |||
| 0207fa04f1 | |||
| 98fdf45fa3 | |||
| 21b3a4bca3 | |||
| 7225fc31da | |||
| eca4810f19 | |||
| 249658c05b | |||
| da82d7a107 | |||
| 08dab2d115 | |||
| 13db1b0db8 | |||
| c1921a811b | |||
| 6f914a4973 | |||
| 473be3d485 | |||
| d7fd39503f | |||
| b4b66f94ec | |||
| 0823d393ed | |||
| cbd36184bd | |||
| 465f754803 | |||
| 8b9265ad96 | |||
| 5f930c262c | |||
| afa95d4799 | |||
| 2fa7c97f39 | |||
| 2b75fcf773 | |||
| 76bdc21fef | |||
| cdff2ef792 | |||
| a740a8f962 | |||
| d1f1c390f6 | |||
| 1c88ce3cc0 | |||
| c4ef1a24c0 | |||
| a79fce907e | |||
| c9d496956c | |||
| 31dce41276 | |||
| c6576dfc4b | |||
| 9048b14fdb | |||
| 43100d11bf | |||
| 4876314cf5 | |||
| 2f75131710 | |||
| 1e09fd6662 | |||
| 48f2c56caa | |||
| 20d83dd476 | |||
| 9c6be78b4c | |||
| 0a8e71771e | |||
| 29d1c7bccd | |||
| ca1996a670 | |||
| ab1c1c474a | |||
| d7cac8a8f0 | |||
| 63bc87cc86 | |||
| 232875d5cc | |||
| f3c5e300cd | |||
| 29072f0285 | |||
| 5ea53ea5c0 | |||
| 40aca0fe73 | |||
| f4a2fb9687 | |||
| 367c505444 | |||
| 3bd39b3ea5 | |||
| e89dcb2cca | |||
| ad65bdde9d | |||
| 34cd611a8b | |||
| d82b71de89 | |||
| 8894a982f2 | |||
| 2cb2ca15c7 | |||
| db41645159 | |||
| a74d1ce9ca | |||
| 2e832520e6 | |||
| 62285a141e | |||
| c3d5a0b8f8 | |||
| a36dbbf422 | |||
| 4cf23bb2e6 | |||
| e2c1f38ed3 | |||
| 5ec1da34b4 | |||
| ea11c1046a | |||
| df40f27069 | |||
| 76d732f247 | |||
| 219400de8d | |||
| 8901d83c94 | |||
| 0c8d4e8dd8 | |||
| a955dcbaa9 | |||
| fbac5134ca | |||
| 45ec6b6e74 | |||
| dd29ff4731 | |||
| 8b94a28e00 | |||
| 60100ad7f0 | |||
| 62a50fd7fc | |||
| b17bdad864 | |||
| 52daa165a2 | |||
| 4c5ba04822 | |||
| 79c2523585 | |||
| f14ad8b3fa | |||
| 590fdacba3 | |||
| 342a2a5568 | |||
| e382687168 | |||
| 4577a40b1e | |||
| c0aacb7d62 | |||
| c8065c8092 | |||
| ce03bfbf0f | |||
| 0182e2c0bc | |||
| e464e11ab9 | |||
| d7ff54d679 | |||
| 4aa1091f62 | |||
| 6d024d2055 | |||
| c86cdf737f | |||
| 7e36b215fe | |||
| badebbef9f | |||
| 5e072c3282 | |||
| 048c3a900c | |||
| 88a9fe410c | |||
| cf32b84257 | |||
| e7dea0a77f | |||
| 160489a771 | |||
| 996c6826b9 | |||
| 43b871a124 | |||
| d1bf186040 | |||
| 24c68f100e | |||
| 1bfabf9a83 | |||
| e8a778feca | |||
| 5d4c10c56e | |||
| 60b1c4d8f7 | |||
| f1404cd3ee | |||
| ee4da8a89c | |||
| 5a70a16149 | |||
| f019ba3713 | |||
| 4b966c4845 | |||
| 94703bcf37 | |||
| 40cc6b54c9 | |||
| 584ea7e9f8 | |||
| cbdbc124db | |||
| b9c3fa9401 | |||
| 0e4ec8a8b8 | |||
| c3e4bb80a8 | |||
| 6459840507 | |||
| 87abbe9396 | |||
| d26a8319b6 | |||
| c9c80fd861 | |||
| fea4cc7b3b | |||
| cba5da22ae | |||
| 59a29da054 | |||
| c70674471e | |||
| 7882324439 | |||
| 1f8866a48a | |||
| faf28a6d4e | |||
| 59745e6fb6 | |||
| c8925cd270 | |||
| e35f3b6056 | |||
| d68014ec7b | |||
| b63029054d | |||
| 849c8bee78 | |||
| 70f0384cc3 | |||
| 5459720523 | |||
| a00e2acb5c | |||
| 1d405076e6 | |||
| 7119c566ef | |||
| 0e9428aaae | |||
| fe009ca235 | |||
| a377384553 | |||
| 03c8c323bc | |||
| fdbc380421 | |||
| 7056134b24 | |||
| 93c7552a41 | |||
| 931ed119bb | |||
| 0580842ad2 | |||
| 8d9db83a87 | |||
| c3eb6b2dbf | |||
| 777ad369a2 | |||
| 715efaa087 | |||
| 606a8f134d | |||
| 84e92ca69f | |||
| 0f0f8b3461 | |||
| 761b98f02f | |||
| b19e16e4b8 | |||
| 407c9fe1a6 | |||
| 0b61f8f146 | |||
| 06eee89479 | |||
| e3a43e4ca8 | |||
| f876ffab52 | |||
| 0dcd4ca133 | |||
| 2562d1e77d | |||
| e1531c200c | |||
| c09bc742d8 | |||
| 29e8d07693 | |||
| 4fd4e8a16e | |||
| 30d627c2be | |||
| 9390cb64b4 | |||
| d720feaa6d | |||
| 9f7cda3b69 | |||
| 878f67a051 | |||
| 7fb8550c97 | |||
| 700836aea0 | |||
| 16aaa1b050 | |||
| 8790d3cfcf | |||
| bb07138fb0 | |||
| 37c650e490 | |||
| 272e3895fd | |||
| 6e7f374b0d | |||
| 3743e45566 | |||
| b10e8abde0 | |||
| 5dab4422e9 | |||
| 82b6037a00 | |||
| 1bdb8b2724 | |||
| 8c905e4f42 | |||
| e9e59a2704 | |||
| e3a1482b8f | |||
| 9539b24d64 | |||
| 87caeef0af | |||
| 757e8a02ec | |||
| 6d0a128111 | |||
| 28b36d379b | |||
| 038b5d1437 | |||
| 038e1794eb | |||
| 663b2cd888 | |||
| 23f14e5799 | |||
| 55572acdc8 | |||
| 08125e9281 | |||
| e8ee9de5b9 | |||
| 91aea0e968 | |||
| 4cba009ac8 | |||
| 47ea4b226a | |||
| 00059e6754 | |||
| e4b81063cb | |||
| 3499fbd758 | |||
| 4b3d4690e8 | |||
| 48480bc839 | |||
| 031ed9c203 | |||
| f551732a17 | |||
| 7a814faed2 | |||
| 792317e945 | |||
| 9c10e06aac | |||
| c39108043b | |||
| 30bf941979 | |||
| 55ee6a9d13 | |||
| 2b25fe1fa4 | |||
| 57d563d488 | |||
| 2ca9ca3cb6 | |||
| ebb04d8a14 | |||
| 3c24ac26d5 | |||
| 87ce5a6d82 | |||
| 9623e2de6f | |||
| b9b4c1c38d | |||
| 688cb30d4a | |||
| 1aca2cde71 | |||
| 49fa451cc3 | |||
| 5f1389f824 | |||
| a90693e488 | |||
| ebeec056cd | |||
| 49d65292c0 | |||
| 6c30a04ac0 | |||
| f070314524 | |||
| 75c88eaa55 | |||
| bd6ae2ac2b | |||
| 58d04f9693 | |||
| 01c12655b8 | |||
| d4198737a6 | |||
| 04881b9b78 | |||
| 990b8cda96 | |||
| 27889b8085 | |||
| 2cd7735468 | |||
| 8990f2d1d6 | |||
| 7bc608ce6c | |||
| 01c7daaba7 | |||
| 8408a5fdc0 | |||
| 828fe0e86e | |||
| 5c3179df48 | |||
| 618cb27ac1 | |||
| 4003e0a2ab | |||
| e87db5b2ab | |||
| 5b9c28e6f0 | |||
| 4375d77a98 | |||
| 83a569b366 | |||
| 842c9c8ecd | |||
| 70244071ea | |||
| f3cc19b09c | |||
| 6b8faf0ecf | |||
| 71ad1e9939 | |||
| f355cb4d38 | |||
| 5ae8d274c0 | |||
| 6402894096 | |||
| 2bb0008eb4 | |||
| 906141e2ae | |||
| 6ac8a4c0bc | |||
| 0827d81617 | |||
| e71e56f7fe | |||
| 7510ba2541 | |||
| a78b2dee46 | |||
| 2cce1c7b2a | |||
| 7533dc952d | |||
| 3408e8427d | |||
| 5795a6a2f0 | |||
| 9f64e8a6fa | |||
| f176174fca | |||
| 2747f3b492 | |||
| 1c374b59d3 | |||
| ae7ae2886f | |||
| b902f1490f | |||
| 8e5040a357 | |||
| 9881011043 | |||
| d4b8f3e1c2 | |||
| b7fff07197 | |||
| 7fa81a7aca | |||
| e0d1e67d4b | |||
| 8049c47aa8 | |||
| 37a46465ba | |||
| ece6d7b2d7 | |||
| 3d4c73f8af | |||
| ed36273755 | |||
| b7be599769 | |||
| c3484dc062 | |||
| 578a12529c | |||
| e601245f01 | |||
| ad1fb47b0d | |||
| e852c5a22f | |||
| 61287d05bf | |||
| 555453bc1a | |||
| cb81175fa0 | |||
| 627bf25791 | |||
| 8d1015caba | |||
| f2db2b9b1d | |||
| 4b6d0d035e | |||
| 57e9310510 | |||
| fd09769ccc | |||
| 029c798eff | |||
| b81fa5ed39 | |||
| 1375f42869 | |||
| 7cb9d62f0c | |||
| 6bd8c6ceb6 | |||
| f954f89747 | |||
| 82788e39f0 | |||
| 0df4f41269 | |||
| a2ab5df7ce | |||
| 1395f1c990 | |||
| c473e987f4 | |||
| b97ffc16ea | |||
| 355ae5f046 | |||
| 1abda7555d | |||
| 520361f7f3 | |||
| c8c9e911f6 | |||
| eb62056755 | |||
| 294d1edfee | |||
| febab47124 | |||
| 8160fe5448 | |||
| 9169499087 | |||
| 81facfd05f | |||
| 054d9b3f09 | |||
| a95eb759ca | |||
| d1f140ebcb | |||
| 721cd9f319 | |||
| e6a780ebd4 | |||
| 9868fae735 | |||
| c22037462e | |||
| 1f0312573a | |||
| 46c0463e43 | |||
| e05b99a0f1 | |||
| 48dfdabaf4 | |||
| 7ed8d76d84 | |||
| 9e6cbcb35e | |||
| 8c2096e813 | |||
| 2972e1273f | |||
| 0ce0e4765b | |||
| 1517dd81e6 | |||
| b517e3cd5b | |||
| a240c4531a | |||
| 7d84ab37f6 | |||
| 6bdcdf7fd2 | |||
| 5e8e92b765 | |||
| 2006984b47 | |||
| aaa8a35ea8 | |||
| 204e320df4 | |||
| 24a0ed41b9 | |||
| 3a08c1cdb6 | |||
| 2ff5731b39 | |||
| eb2423b0ed | |||
| e60bbaa60f | |||
| 65cc1d5ccf | |||
| f17b630b12 | |||
| 50da1e4704 | |||
| 515a8689e9 | |||
| 04b30fd694 | |||
| e5095b2154 | |||
| 1e48ab4b9c | |||
| fe5e8ce7f7 | |||
| 319d51cb80 | |||
| 14cad02b5a | |||
| bc30a9db68 | |||
| c7cfcb29f6 | |||
| e087a7972e | |||
| 49b3c18903 | |||
| 4f3748a4f0 | |||
| 27cbcc6f5e | |||
| ed2d70dd15 | |||
| ae87d7b236 | |||
| 31fb878bbd | |||
| 59278913ca | |||
| 2023df3ef8 | |||
| 48cf89b1a6 | |||
| 223b14e556 | |||
| 112d79c2be | |||
| 2aec508e43 | |||
| d0b13a8684 | |||
| 28d78453b7 | |||
| 04f2dd1a0b | |||
| 8b0024d53e | |||
| 098685ec8b | |||
| 6c9293ec14 | |||
| 8cbbfb0e34 | |||
| bbbc18b959 | |||
| 5aa495b240 | |||
| c08d0eff7a | |||
| 34213d1607 | |||
| 82aa0b270c | |||
| 847d6de6bf | |||
| 739fe826b3 | |||
| 6bf67917fb | |||
| 4c4c592f31 | |||
| 8a08d146bc | |||
| da41398340 | |||
| d74873be31 | |||
| b9ffa96e8b | |||
| 8a666dc8cc | |||
| 78fc5ec458 | |||
| d093488522 | |||
| 1e29a5210f | |||
| c548ba85fe | |||
| 8bb60afabd | |||
| 8b5cb7729c | |||
| f8d7b98d05 | |||
| bc7912e8fb | |||
| 0812491f09 | |||
| af542a2fc1 | |||
| 039d1b7f99 | |||
| 4ded8784fc | |||
| 943d95a725 | |||
| 75b788b793 | |||
| 048a83c8c9 | |||
| e92badef0e | |||
| 924a423488 | |||
| 1ad821b2b7 | |||
| 62d62474fb | |||
| 1dbc9a1366 | |||
| 99745ac067 | |||
| dbfb7572a8 | |||
| a213b48f93 | |||
| b0f939bfaf | |||
| 075e1ef236 | |||
| 88ad98ed37 | |||
| 0a972285a6 | |||
| 358a2e5266 | |||
| 5bb2eeafb7 | |||
| 94f84625d4 | |||
| 34e4625b64 | |||
| a797c01943 | |||
| b72de5e3a4 | |||
| bf4afae5d9 | |||
| 29dcd5450f | |||
| f1160a11af | |||
| d9762010fa | |||
| 93d9ae32fc | |||
| cb42358e2f | |||
| 8f420d728c | |||
| df818bc2b8 | |||
| 2fbecc4675 | |||
| a553ced979 | |||
| 82987a1835 | |||
| 3c4e8730ac | |||
| d738fdff57 | |||
| d4c1016e55 | |||
| b1ce2dd73f | |||
| d066e32719 | |||
| 7f6094750e | |||
| e4c08be28e | |||
| 7e03de0a21 | |||
| d4da325e57 | |||
| 5a4f733518 | |||
| fd80848fcd | |||
| cab5ee6752 | |||
| 0bc99dbd4f | |||
| 83339da26c | |||
| 8749d5dc7d | |||
| 2bda47fcad | |||
| 85c0d6f837 | |||
| 04d9fa8f9e | |||
| 7b7a2068ea | |||
| 4a31017332 | |||
| 784896434d | |||
| d376b88cf0 | |||
| c7a5b8559c | |||
| fd0c262645 | |||
| 4f7cb43c8f | |||
| 2f40b030ec | |||
| d2b1b9d34c | |||
| b594b5f90a | |||
| 94e219137e | |||
| a26db09e54 | |||
| 351c019310 | |||
| 14fbdb5e04 | |||
| dabc9717d1 | |||
| 5adbf74cbe | |||
| 3e54885ea0 | |||
| 247e676b41 | |||
| cb04dabea8 | |||
| 7df2d70dbf | |||
| 350544e801 | |||
| d6260d960c | |||
| d0fb3509cc | |||
| 798cd5caf3 | |||
| 35fa43f47c | |||
| 4df8ce1b58 | |||
| 83c7396f2d | |||
| 709922c383 | |||
| 16978b8949 | |||
| 7745b68228 | |||
| 036a416a25 | |||
| c9808d07df | |||
| 8e34b51c77 | |||
| afc5307a23 | |||
| 1919610793 | |||
| 6fbf6d90dc | |||
| 828385b049 | |||
| 6bbaf03f1f | |||
| 6fdc8bd379 | |||
| 0f125196a6 | |||
| 974735d415 | |||
| 472b96795f | |||
| 81f4ef609b | |||
| 80d3f7d179 | |||
| c4343e0124 | |||
| 04b6571cb8 | |||
| a7a7d9a3d4 | |||
| 395e7b54f6 | |||
| c20143c212 | |||
| 1729c085c7 | |||
| bf29090ffa | |||
| ca132881f9 | |||
| d47b5b99c5 | |||
| e0ff30e9a8 | |||
| 7c62312220 | |||
| b36972ce71 | |||
| 023e7b2d32 | |||
| e10cd2a3ed | |||
| 209c315a76 | |||
| 7fe2c094a9 | |||
| 23d3e54ddb | |||
| 6b2b98a262 | |||
| fba8568474 | |||
| 2a97939807 | |||
| a74b025de3 | |||
| cec44be7c3 | |||
| a4852c1b36 | |||
| ef2dea89b4 | |||
| 593d86f3a7 | |||
| fd63611b41 | |||
| 4dc32dc7f2 | |||
| 1e845adc17 | |||
| fb6435e30d | |||
| 6ee71d238b | |||
| d330000c8d | |||
| 1517a7b665 | |||
| da33a6c48c | |||
| 89e07921f1 | |||
| 2450511555 | |||
| da1ee99c53 | |||
| 1c922ca083 | |||
| 14a578f319 | |||
| 4a5c411665 | |||
| 0de30afba1 | |||
| 245f2afeac | |||
| 0f81286ff5 | |||
| f01c70e506 | |||
| 03e14154a6 | |||
| 509a767e50 | |||
| e7526f2e78 | |||
| 9d69a2e565 | |||
| 705875cff2 | |||
| 39b366ee69 | |||
| edd326efd9 | |||
| 4f634689c2 | |||
| db429bd838 | |||
| cce372fc50 | |||
| df7479f506 | |||
| 0badd69409 | |||
| c953b8030a | |||
| ba9368426c | |||
| 2cb739027b | |||
| 120ac6c480 | |||
| 6ac68984f2 | |||
| 51633e000b | |||
| b536b8707e | |||
| 3b5f931f06 | |||
| bf15eebd2d | |||
| 4fc22e25ba | |||
| 0f9a9a377b | |||
| d79e6f2704 | |||
| e9672e6bba | |||
| 9670e29d9f | |||
| 612fb7ad7b | |||
| 1da1188351 | |||
| 39433fe707 | |||
| 3b0bc1ca15 | |||
| cc9ad17ea5 | |||
| f965d01922 | |||
| 7cb9546d21 | |||
| debe87f2f5 | |||
| cca2807256 | |||
| 7b73f76e78 | |||
| b1eefd6c85 | |||
| bbcb7ad980 | |||
| 984c43cd75 | |||
| ec4c0fdd09 | |||
| 51d4a9c7ee | |||
| 19930f63e2 | |||
| 3b9a3aaad2 | |||
| f5148074fd | |||
| a949a113cf | |||
| 227e9df419 | |||
| 2a6d462be1 | |||
| bb03fa26cd | |||
| 9eb4703d7a | |||
| 105752fc65 | |||
| 2747e93316 | |||
| 9548f984eb | |||
| cb871ce4bc | |||
| 8ca849b7a8 | |||
| 4bb29b1b5c | |||
| e55e893c94 | |||
| 5ab63a290e | |||
| 7c3414b86f | |||
| cec8829032 | |||
| 78f9f49a8a | |||
| 5a7722fd18 | |||
| d111a979f7 | |||
| 31514c8e31 | |||
| af5ce101ef | |||
| 075da27d13 | |||
| 7b19fb44a4 | |||
| c991946ea7 | |||
| f960a3ae38 | |||
| 73f8811a4b | |||
| bc6ec2579a | |||
| 35bc7263da | |||
| cc3db00a06 | |||
| 7f7961ae0c | |||
| aae60b2ef8 | |||
| ab700543b9 | |||
| 413488f5f4 | |||
| 0ceee14952 | |||
| b4b998df08 | |||
| d6bb165de5 | |||
| ac69f63c89 | |||
| ce5b6c9f64 | |||
| 9d800324af | |||
| e0603f741f | |||
| ce006d0e5b | |||
| 5fb9a9f164 | |||
| 351cd29050 | |||
| 6c160b719a | |||
| d1a7ca7822 | |||
| ee515394c0 | |||
| b2efed71d3 | |||
| 9035dc6bf7 | |||
| 6e5a25dac4 | |||
| af80b07b01 | |||
| 0d93fdf23d | |||
| db2379e2fd | |||
| 9a3900114b | |||
| 6b1d689621 | |||
| 2f7ce565f0 | |||
| 77b9cab07b | |||
| f9fe4e9c3d | |||
| cd32f0ff6b | |||
| 756a796e1d | |||
| 72e949c644 | |||
| 58ba3b012e | |||
| 1854256a93 | |||
| a31bf17469 | |||
| ca7d7ab675 | |||
| 20c802a1e5 | |||
| d1cbf4f06c | |||
| 86443252b1 | |||
| 653727fd12 | |||
| 7a3354f654 | |||
| e9ebee180e | |||
| a93259f3bd | |||
| 8f6c012fb3 | |||
| a635b023f6 | |||
| 1cc7ea5ca7 | |||
| 1b9f874db5 | |||
| cf5ae8f291 | |||
| 96878e2247 | |||
| 80fad573fa | |||
| 1d2a1eee81 | |||
| baecdc4d4f | |||
| 310e6ffc0d | |||
| 13f6e50354 | |||
| f76aec8b5a | |||
| 40fb9de15e | |||
| 0630edc626 | |||
| f2ef6fa12f | |||
| e9616a2d3e | |||
| de5a4cd8cb | |||
| 3b18f12ff2 | |||
| 88bb7a7e5b | |||
| 8fe4ce456f | |||
| 8a7c56e8fd | |||
| 43ac21fd66 | |||
| 17a854e8e1 | |||
| 09c67dd557 | |||
| d837b409e8 | |||
| 994a000e36 | |||
| 5ae50047e0 | |||
| 4e47e7ac2a | |||
| 22a3549599 | |||
| 8bb2a399cc | |||
| 2780dc6a67 | |||
| 421129029d | |||
| cd35df6cc5 | |||
| 61c787b1c7 | |||
| 3c6f80e520 | |||
| 8592153c0f | |||
| 958334bd49 | |||
| 6bbe2d0e00 | |||
| 9786deef48 | |||
| 9af1c1671c | |||
| ce743fe95d | |||
| a9c038bcb6 | |||
| 35bc5de40f | |||
| fb1494fc81 | |||
| e3da0fe255 | |||
| 4443e39785 | |||
| 796c617569 | |||
| 275a92ae93 | |||
| 35d2cc9be7 | |||
| 264c2b2f90 | |||
| a520d636e8 | |||
| 090aaf8ee3 | |||
| 34a9d1d125 | |||
| 40b3f77db0 | |||
| 0c7453684b | |||
| 310c6a1ccf | |||
| 4c52a12507 | |||
| 1a8e4c953d | |||
| 8bf33e211d | |||
| c49c296d2b | |||
| ee5a126c1c | |||
| e4f08f79c3 | |||
| 2aaec3b6bd | |||
| 743a2f8dac | |||
| aa5c3042da | |||
| f221fead4a | |||
| af51018e02 | |||
| ed904c2bdd | |||
| 4ed9625959 | |||
| 42e9b6d2f3 | |||
| a8788feb50 | |||
| 22a8aab151 | |||
| f44d1c4b9d | |||
| a28bd09365 | |||
| 345cc45a3e | |||
| 3f189c430b | |||
| 207ff70680 | |||
| 5113d52444 | |||
| fd8abc168d | |||
| 033139677b | |||
| 2e4128dcfe | |||
| 0a1f349901 | |||
| 62a589b6ad | |||
| 055829dcf8 | |||
| 649364beb5 | |||
| d3f9756bdb | |||
| 7447d9a55a | |||
| 70511dd0f2 | |||
| 664f81249c | |||
| 8f2e616e07 | |||
| 72708d6e2c | |||
| 7a633ee8c8 | |||
| c11fe3e1ab | |||
| a4e54f063d | |||
| b5321f8993 | |||
| bcf799732f | |||
| 13ba2182c2 | |||
| 0d25c607e7 | |||
| b3f8866ef7 | |||
| 9bb16dec48 | |||
| bdb35f1c1d | |||
| d421b5aa5a | |||
| 1ec05e8a6c | |||
| 5b941013de | |||
| a93ed35eee | |||
| 76469969f3 | |||
| 8b39ea4acb | |||
| 252ca9a5f9 | |||
| c4eb1a0f5b | |||
| 1e2f4e9ebb | |||
| 2a7aefac45 | |||
| ea39e2d842 | |||
| fc5879a204 | |||
| 5ae2229e37 | |||
| 12e5ce0ff0 | |||
| 5ef3774d11 | |||
| 654e816e6b | |||
| 7cad7bcddb | |||
| 136d514cf7 | |||
| 6e48345d54 | |||
| 8ebdb466f7 | |||
| 1ed7b690a5 | |||
| 5c28a3eda7 | |||
| f40f002bf9 | |||
| 69d1789a03 | |||
| 4edf2eb92c | |||
| 098956b81a | |||
| 2aa665ae38 |
16
.gitignore
vendored
@ -5,11 +5,14 @@
|
|||||||
# Editor files
|
# Editor files
|
||||||
.*.sw?
|
.*.sw?
|
||||||
*~
|
*~
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
|
||||||
# Test files
|
# Test files
|
||||||
godog.test
|
godog.test
|
||||||
debug.test
|
debug.test
|
||||||
coverage.html
|
coverage.html
|
||||||
|
gobinsec-cache*.yml
|
||||||
|
|
||||||
# Run files
|
# Run files
|
||||||
mem.pprof
|
mem.pprof
|
||||||
@ -29,3 +32,16 @@ vendor-cache
|
|||||||
/hasher
|
/hasher
|
||||||
cmd/Desktop-Bridge/deploy
|
cmd/Desktop-Bridge/deploy
|
||||||
cmd/Import-Export/deploy
|
cmd/Import-Export/deploy
|
||||||
|
proton-bridge
|
||||||
|
cmd/Desktop-Bridge/*.exe
|
||||||
|
cmd/launcher/*.exe
|
||||||
|
|
||||||
|
# Jetbrains (CLion, Golang) cmake build dirs
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Doxygen doc files
|
||||||
|
_doc/
|
||||||
|
|
||||||
|
# gRPC auto-generated C++ source files
|
||||||
|
*.pb.cc
|
||||||
|
*.pb.h
|
||||||
|
|||||||
274
.gitlab-ci.yml
@ -1,248 +1,42 @@
|
|||||||
image: gitlab.protontech.ch:4567/go/bridge-internal:latest
|
# Copyright (c) 2022 Proton Technologies AG
|
||||||
|
#
|
||||||
|
# This file is part of ProtonMail Bridge.
|
||||||
|
#
|
||||||
|
# ProtonMail Bridge 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 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# ProtonMail Bridge 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
---
|
||||||
|
image: gitlab.protontech.ch:4567/go/bridge-internal:test-go1.20
|
||||||
|
|
||||||
|
default:
|
||||||
|
tags:
|
||||||
|
- shared-small
|
||||||
|
|
||||||
|
variables:
|
||||||
|
GOPRIVATE: gitlab.protontech.ch
|
||||||
|
GOMAXPROCS: $(( ${CI_TAG_CPU} / 2 ))
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- eval $(ssh-agent -s)
|
- apt update && apt-get -y install libsecret-1-dev
|
||||||
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
|
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
||||||
|
|
||||||
- mkdir -p .cache/bin
|
|
||||||
- export PATH=$(pwd)/.cache/bin:$PATH
|
|
||||||
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
|
||||||
|
|
||||||
- make install-dev-dependencies
|
|
||||||
|
|
||||||
cache:
|
|
||||||
key: go-mod
|
|
||||||
paths:
|
|
||||||
- .cache
|
|
||||||
policy: pull
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- cache
|
|
||||||
- test
|
- test
|
||||||
- build
|
- build
|
||||||
- mirror
|
|
||||||
|
|
||||||
# Stage: CACHE
|
include:
|
||||||
|
- local: ci/rules.yml
|
||||||
|
- local: ci/env.yml
|
||||||
|
- local: ci/test.yml
|
||||||
|
- local: ci/build.yml
|
||||||
|
|
||||||
# This will ensure latest dependency versions and updates the cache for
|
|
||||||
# all other following jobs which only pull the cache.
|
|
||||||
cache-push:
|
|
||||||
stage: cache
|
|
||||||
only:
|
|
||||||
- branches
|
|
||||||
script:
|
|
||||||
- echo ""
|
|
||||||
cache:
|
|
||||||
key: go-mod
|
|
||||||
paths:
|
|
||||||
- .cache
|
|
||||||
|
|
||||||
# Stage: TEST
|
|
||||||
|
|
||||||
lint:
|
|
||||||
stage: test
|
|
||||||
only:
|
|
||||||
- branches
|
|
||||||
before_script:
|
|
||||||
- mkdir -p .cache/bin
|
|
||||||
- export PATH=$(pwd)/.cache/bin:$PATH
|
|
||||||
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
|
||||||
script:
|
|
||||||
- env GOMAXPROCS=$(( ${CI_TAG_CPU} / 2 )) make lint
|
|
||||||
tags:
|
|
||||||
- medium
|
|
||||||
|
|
||||||
test-linux:
|
|
||||||
stage: test
|
|
||||||
only:
|
|
||||||
- branches
|
|
||||||
script:
|
|
||||||
- apt-get -y install pass gnupg rng-tools
|
|
||||||
# First have enough of entropy (cat /proc/sys/kernel/random/entropy_avail).
|
|
||||||
- rngd -r /dev/urandom
|
|
||||||
# Generate GPG key without password for the password manager.
|
|
||||||
- gpg --batch --yes --passphrase '' --quick-generate-key 'tester@example.com'
|
|
||||||
# Use the last created GPG ID for the password manager.
|
|
||||||
- pass init `gpg --list-keys | grep "^ " | tail -1 | tr -d '[:space:]'`
|
|
||||||
# Then finally run the tests
|
|
||||||
- make test
|
|
||||||
tags:
|
|
||||||
- medium
|
|
||||||
|
|
||||||
test-windows:
|
|
||||||
extends: .build-windows-base
|
|
||||||
stage: test
|
|
||||||
only:
|
|
||||||
- branches
|
|
||||||
script:
|
|
||||||
- make test
|
|
||||||
|
|
||||||
test-integration:
|
|
||||||
stage: test
|
|
||||||
only:
|
|
||||||
- branches
|
|
||||||
script:
|
|
||||||
- VERBOSITY=debug make -C test test
|
|
||||||
tags:
|
|
||||||
- large
|
|
||||||
|
|
||||||
dependency-updates:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- make updates
|
|
||||||
|
|
||||||
# Stage: BUILD
|
|
||||||
|
|
||||||
build-qml:
|
|
||||||
tags:
|
|
||||||
- small
|
|
||||||
only:
|
|
||||||
- branches
|
|
||||||
stage: build
|
|
||||||
artifacts:
|
|
||||||
name: "bridge-qml-$CI_COMMIT_SHORT_SHA"
|
|
||||||
expire_in: 1 day
|
|
||||||
paths:
|
|
||||||
- bridge_qml.tgz
|
|
||||||
script:
|
|
||||||
- cd internal/frontend/qml
|
|
||||||
- tar -cvzf ../../../bridge_qml.tgz ./*
|
|
||||||
|
|
||||||
|
|
||||||
.build-base:
|
|
||||||
stage: build
|
|
||||||
only:
|
|
||||||
- manual
|
|
||||||
before_script:
|
|
||||||
- mkdir -p .cache/bin
|
|
||||||
- export PATH=$(pwd)/.cache/bin:$PATH
|
|
||||||
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
|
||||||
script:
|
|
||||||
- make build
|
|
||||||
- git diff && git diff-index --quiet HEAD
|
|
||||||
artifacts:
|
|
||||||
# Note: The latest artifacts for refs are locked against deletion, and kept
|
|
||||||
# regardless of the expiry time. Introduced in GitLab 13.0 behind a
|
|
||||||
# disabled feature flag, and made the default behavior in GitLab 13.4.
|
|
||||||
expire_in: 1 day
|
|
||||||
tags:
|
|
||||||
- large
|
|
||||||
|
|
||||||
build-linux:
|
|
||||||
extends: .build-base
|
|
||||||
artifacts:
|
|
||||||
name: "bridge-linux-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- bridge_*.tgz
|
|
||||||
|
|
||||||
build-linux-qa:
|
|
||||||
extends: .build-base
|
|
||||||
only:
|
|
||||||
- web
|
|
||||||
- branches
|
|
||||||
script:
|
|
||||||
- BUILD_TAGS="build_qa" make build
|
|
||||||
artifacts:
|
|
||||||
name: "bridge-linux-qa-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- bridge_*.tgz
|
|
||||||
|
|
||||||
|
|
||||||
.build-darwin-base:
|
|
||||||
extends: .build-base
|
|
||||||
before_script:
|
|
||||||
- export PATH=/usr/local/bin:$PATH
|
|
||||||
- export PATH=/usr/local/opt/git/bin:$PATH
|
|
||||||
- export PATH=/usr/local/opt/make/libexec/gnubin:$PATH
|
|
||||||
- export PATH=/usr/local/opt/go@1.13/bin:$PATH
|
|
||||||
- export PATH=/usr/local/opt/gnu-sed/libexec/gnubin:$PATH
|
|
||||||
- export GOPATH=~/go
|
|
||||||
- export PATH=$GOPATH/bin:$PATH
|
|
||||||
- export CGO_CPPFLAGS='-Wno-error -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-builtin-requires-header'
|
|
||||||
cache: {}
|
|
||||||
tags:
|
|
||||||
- macOS
|
|
||||||
|
|
||||||
build-darwin:
|
|
||||||
extends: .build-darwin-base
|
|
||||||
artifacts:
|
|
||||||
name: "bridge-darwin-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- bridge_*.tgz
|
|
||||||
|
|
||||||
build-darwin-qa:
|
|
||||||
extends: .build-darwin-base
|
|
||||||
only:
|
|
||||||
- web
|
|
||||||
- branches
|
|
||||||
script:
|
|
||||||
- BUILD_TAGS="build_qa" make build
|
|
||||||
artifacts:
|
|
||||||
name: "bridge-darwin-qa-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- bridge_*.tgz
|
|
||||||
|
|
||||||
|
|
||||||
.build-windows-base:
|
|
||||||
extends: .build-base
|
|
||||||
before_script:
|
|
||||||
- export GOROOT=/c/Go
|
|
||||||
- export PATH=$GOROOT/bin:$PATH
|
|
||||||
- export GOARCH=amd64
|
|
||||||
- export GOPATH=~/go
|
|
||||||
- export GO111MODULE=on
|
|
||||||
- export PATH=$GOPATH/bin:$PATH
|
|
||||||
- export MSYSTEM=
|
|
||||||
- export PATH=$PATH:/c/grrrQt/5.13.2/mingw73_64/bin
|
|
||||||
tags:
|
|
||||||
- windows-bridge
|
|
||||||
|
|
||||||
build-windows:
|
|
||||||
extends: .build-windows-base
|
|
||||||
artifacts:
|
|
||||||
name: "bridge-windows-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- bridge_*.tgz
|
|
||||||
|
|
||||||
build-windows-qa:
|
|
||||||
extends: .build-windows-base
|
|
||||||
only:
|
|
||||||
- web
|
|
||||||
- branches
|
|
||||||
script:
|
|
||||||
- BUILD_TAGS="build_qa" make build
|
|
||||||
artifacts:
|
|
||||||
name: "bridge-windows-qa-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- bridge_*.tgz
|
|
||||||
|
|
||||||
# Stage: MIRROR
|
|
||||||
|
|
||||||
mirror-repo:
|
|
||||||
stage: mirror
|
|
||||||
only:
|
|
||||||
refs:
|
|
||||||
- master
|
|
||||||
script:
|
|
||||||
- |
|
|
||||||
cat <<EOF > ~/.ssh/config
|
|
||||||
Host github.com
|
|
||||||
Hostname ssh.github.com
|
|
||||||
User git
|
|
||||||
Port 443
|
|
||||||
ProxyCommand connect-proxy -H $http_proxy %h %p
|
|
||||||
EOF
|
|
||||||
- ssh-keyscan -t rsa ${CI_SERVER_HOST} > ~/.ssh/known_hosts
|
|
||||||
- |
|
|
||||||
cat <<EOF >> ~/.ssh/known_hosts
|
|
||||||
# ssh.github.com:443 SSH-2.0-babeld-2e9d163d
|
|
||||||
[ssh.github.com]:443 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
|
|
||||||
EOF
|
|
||||||
- echo "$mirror_key" | tr -d '\r' | ssh-add - > /dev/null
|
|
||||||
- ssh-add -l
|
|
||||||
- git clone "$CI_REPOSITORY_URL" --branch master _REPO_CLONE;
|
|
||||||
- cd _REPO_CLONE
|
|
||||||
- git remote add public $mirror_url
|
|
||||||
- git push public master
|
|
||||||
# Pushing the latest tag from master history
|
|
||||||
- git push public "$(git describe --tags --abbrev=0 || echo master)"
|
|
||||||
|
|||||||
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "submodules/vcpkg"]
|
||||||
|
path = extern/vcpkg
|
||||||
|
url = https://github.com/Microsoft/vcpkg.git
|
||||||
@ -3,6 +3,7 @@ run:
|
|||||||
timeout: 10m
|
timeout: 10m
|
||||||
skip-dirs:
|
skip-dirs:
|
||||||
- pkg/mime
|
- pkg/mime
|
||||||
|
- extern
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
@ -12,15 +13,37 @@ issues:
|
|||||||
- should have comment (\([^)]+\) )?or be unexported
|
- should have comment (\([^)]+\) )?or be unexported
|
||||||
# For now we are missing a lot of comments.
|
# For now we are missing a lot of comments.
|
||||||
- at least one file in a package should have a package comment
|
- at least one file in a package should have a package comment
|
||||||
|
# Package comments.
|
||||||
|
- "package-comments: should have a package comment"
|
||||||
|
# Migration uses underscores to make versions clearer.
|
||||||
|
- "var-naming: don't use underscores in Go names"
|
||||||
|
- "ST1003: should not use underscores in Go names"
|
||||||
|
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
- funlen
|
|
||||||
- gochecknoglobals
|
- gochecknoglobals
|
||||||
- gochecknoinits
|
- gochecknoinits
|
||||||
- gosec
|
- gosec
|
||||||
|
- goconst
|
||||||
|
- dogsled
|
||||||
|
- path: test
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
- gosec
|
||||||
|
- goconst
|
||||||
|
- dogsled
|
||||||
|
- path: utils/smtp-send
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
- gosec
|
||||||
|
- goconst
|
||||||
|
- dogsled
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
godox:
|
godox:
|
||||||
@ -33,21 +56,17 @@ linters:
|
|||||||
disable-all: true
|
disable-all: true
|
||||||
|
|
||||||
enable:
|
enable:
|
||||||
- deadcode # Finds unused code [fast: true, auto-fix: false]
|
|
||||||
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false]
|
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false]
|
||||||
- gosimple # Linter for Go source code that specializes in simplifying a code [fast: true, auto-fix: false]
|
- gosimple # Linter for Go source code that specializes in simplifying a code [fast: true, auto-fix: false]
|
||||||
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true, auto-fix: false]
|
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true, auto-fix: false]
|
||||||
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
|
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
|
||||||
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false]
|
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false]
|
||||||
- structcheck # Finds unused struct fields [fast: true, auto-fix: false]
|
|
||||||
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
|
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
|
||||||
- unused # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
|
- unused # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
|
||||||
- varcheck # Finds unused global variables and constants [fast: true, auto-fix: false]
|
|
||||||
- bodyclose # checks whether HTTP response body is closed successfully [fast: true, auto-fix: false]
|
- bodyclose # checks whether HTTP response body is closed successfully [fast: true, auto-fix: false]
|
||||||
- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
|
#- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
|
||||||
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
|
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
|
||||||
- dupl # Tool for code clone detection [fast: true, auto-fix: false]
|
- dupl # Tool for code clone detection [fast: true, auto-fix: false]
|
||||||
- funlen # Tool for detection of long functions [fast: true, auto-fix: false]
|
|
||||||
- gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false]
|
- gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false]
|
||||||
- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
|
- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
|
||||||
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
|
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
|
||||||
@ -106,3 +125,7 @@ linters:
|
|||||||
# - thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers [fast: false, auto-fix: false]
|
# - thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers [fast: false, auto-fix: false]
|
||||||
# - wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
|
# - wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
|
||||||
|
|
||||||
|
# Deprecated:
|
||||||
|
# - structcheck # Finds unused struct fields [fast: true, auto-fix: false] deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused.
|
||||||
|
# - deadcode # Finds unused code [fast: true, auto-fix: false] deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused.
|
||||||
|
# - varcheck # Finds unused global variables and constants [fast: true, auto-fix: false] deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused.
|
||||||
|
|||||||
56
BUILDS.md
@ -1,31 +1,28 @@
|
|||||||
# Building Proton Mail Bridge and Import-Export app
|
# Building Proton Mail Bridge
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
* 64-bit AMD OS:
|
* 64-bit OS:
|
||||||
- the go-rfc5322 module cannot currently be compiled for 32-bit OSes
|
- the go-rfc5322 module cannot currently be compiled for 32-bit OSes
|
||||||
- the Apple M1 builds are not supported yet due to dependencies
|
* Go 1.20
|
||||||
* Go 1.13
|
|
||||||
* Bash with basic build utils: make, gcc, sed, find, grep, ...
|
* Bash with basic build utils: make, gcc, sed, find, grep, ...
|
||||||
* For Windows it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
|
- For Windows, it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
|
||||||
* GCC (linux, windows) or Xcode (macOS)
|
* GCC (Linux), msvc (Windows) or Xcode (macOS)
|
||||||
* Windres (windows)
|
* Windres (Windows)
|
||||||
* libglvnd and libsecret development files (linux)
|
* libglvnd and libsecret development files (Linux)
|
||||||
|
* pkg-config (Linux)
|
||||||
|
* cmake, ninja-build and Qt 6.4.3 are required to build the graphical user interface. On Linux,
|
||||||
|
the Mesa OpenGL development files are also needed.
|
||||||
|
|
||||||
To enable the sending of crash reports using Sentry please set the
|
To enable the sending of crash reports using Sentry please set the
|
||||||
`main.DSNSentry` value with the client key of your sentry project before build.
|
`DSN_SENTRY` environment variable with the client key of your sentry project before build.
|
||||||
Otherwise, the sending of crash reports will be disabled.
|
Otherwise, the sending of crash reports will be disabled.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
In order to build Bridge or Import-Export app with Qt interface we are using
|
In order to build Bridge app with Qt interface we are using
|
||||||
[Qt Go Binding](https://github.com/therecipe/qt). The dependencies and
|
[Qt 6.4.3](https://doc.qt.io/qt-6/gettingstarted.html).
|
||||||
installation of this tool is part of `make build` target. If you have issues
|
|
||||||
with installation of therecipe/qt we recommend to follow [this
|
|
||||||
wiki](https://github.com/therecipe/qt/wiki/Installation-on-Linux)
|
|
||||||
|
|
||||||
Please note that `$(go env GOPATH)/bin` must be in your `PATH` to ensure
|
|
||||||
binaries installed by `therecipe/qt` (such as `qtdeploy`) are found. Also,
|
|
||||||
before you start build **on Windows**, please unset the `MSYSTEM` variable
|
|
||||||
|
|
||||||
|
Please note that qmake path must be in your `PATH` to ensure Qt to be found.
|
||||||
|
Also, before you start build **on Windows**, please unset the `MSYSTEM` variable
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export MSYSTEM=
|
export MSYSTEM=
|
||||||
@ -50,28 +47,17 @@ make build
|
|||||||
make build-nogui
|
make build-nogui
|
||||||
```
|
```
|
||||||
|
|
||||||
* Bridge without GUI will start by default without any interface (i.e., there is no way to add or remove client, get bridge password, etc)
|
* To launch Bridge without GUI, you can invoke the `bridge` executable with one the following command-line switches:
|
||||||
* Bridge always has the option (whether built with Qt or without) to use a CLI interface by starting it with the argument `-c`
|
* `--noninteractive` or `-n` to start Bridge without any interface (i.e., there is no way to add or remove client, get bridge password, etc.)
|
||||||
* NOTE: You still need to setup supported keychain on your system
|
* `--cli` or `-c` to start Bridge with an interactive terminal interface.
|
||||||
|
* NOTE: You still need to set up a supported keychain on your system.
|
||||||
|
|
||||||
### Build Import-Export
|
## Launchers
|
||||||
* in project root run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make build-ie
|
|
||||||
```
|
|
||||||
|
|
||||||
* The result will be stored in `./cmd/Import-Export/deploy/${GOOS}/`
|
|
||||||
* for `linux`, the binary will have the name of the project directory (e.g `proton-bridge`)
|
|
||||||
* for `windows`, the binary will have the file extension `.exe` (e.g `proton-bridge.exe`)
|
|
||||||
* for `darwin`, the application will be created with name of the project directory (e.g `proton-bridge.app`)
|
|
||||||
|
|
||||||
### Launchers
|
|
||||||
Launchers are only included in official distributions and provide the public
|
Launchers are only included in official distributions and provide the public
|
||||||
key used to verify signed app binaries, allowing the automatic update feature.
|
key used to verify signed app binaries, allowing the automatic update feature.
|
||||||
See README for more information.
|
See README for more information.
|
||||||
|
|
||||||
### Tags
|
## Tags
|
||||||
Note that repository contains both Bridge and Import-Export apps and they are
|
Note that repository contains both Bridge and Import-Export apps and they are
|
||||||
not released together. Therefore, each app has own tag prefix. Bridge tags
|
not released together. Therefore, each app has own tag prefix. Bridge tags
|
||||||
starts with `br-` and Import-Export tags starts with `ie-`. Both tags continue
|
starts with `br-` and Import-Export tags starts with `ie-`. Both tags continue
|
||||||
|
|||||||
113
COPYING_NOTES.md
@ -21,39 +21,27 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [Qt](https://www.qt.io/) | Available under [multiple licences](https://www.qt.io/licensing)
|
* [Qt](https://www.qt.io/) | Available under [multiple licences](https://www.qt.io/licensing)
|
||||||
|
|
||||||
<!-- START AUTOGEN -->
|
<!-- START AUTOGEN -->
|
||||||
* [docker-credential-helpers](https://github.com/docker/docker-credential-helpers) available under [license](https://github.com/docker/docker-credential-helpers/blob/master/LICENSE)
|
|
||||||
* [go-imap](https://github.com/emersion/go-imap) available under [license](https://github.com/emersion/go-imap/blob/master/LICENSE)
|
|
||||||
* [bcrypt](https://github.com/jameskeane/bcrypt) available under [license](https://github.com/jameskeane/bcrypt/blob/master/LICENSE)
|
|
||||||
* [notificator](https://github.com/0xAX/notificator) available under [license](https://github.com/0xAX/notificator/blob/master/LICENSE)
|
* [notificator](https://github.com/0xAX/notificator) available under [license](https://github.com/0xAX/notificator/blob/master/LICENSE)
|
||||||
* [semver](https://github.com/Masterminds/semver/v3) available under [license](https://github.com/Masterminds/semver/v3/blob/master/LICENSE)
|
* [semver](https://github.com/Masterminds/semver/v3) available under [license](https://github.com/Masterminds/semver/v3/blob/master/LICENSE)
|
||||||
|
* [gluon](https://github.com/ProtonMail/gluon) available under [license](https://github.com/ProtonMail/gluon/blob/master/LICENSE)
|
||||||
* [go-autostart](https://github.com/ProtonMail/go-autostart) available under [license](https://github.com/ProtonMail/go-autostart/blob/master/LICENSE)
|
* [go-autostart](https://github.com/ProtonMail/go-autostart) available under [license](https://github.com/ProtonMail/go-autostart/blob/master/LICENSE)
|
||||||
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
|
* [go-proton-api](https://github.com/ProtonMail/go-proton-api) available under [license](https://github.com/ProtonMail/go-proton-api/blob/master/LICENSE)
|
||||||
* [go-imap-id](https://github.com/ProtonMail/go-imap-id) available under [license](https://github.com/ProtonMail/go-imap-id/blob/master/LICENSE)
|
|
||||||
* [go-rfc5322](https://github.com/ProtonMail/go-rfc5322) available under [license](https://github.com/ProtonMail/go-rfc5322/blob/master/LICENSE)
|
|
||||||
* [go-srp](https://github.com/ProtonMail/go-srp) available under [license](https://github.com/ProtonMail/go-srp/blob/master/LICENSE)
|
|
||||||
* [go-vcard](https://github.com/ProtonMail/go-vcard) available under [license](https://github.com/ProtonMail/go-vcard/blob/master/LICENSE)
|
|
||||||
* [gopenpgp](https://github.com/ProtonMail/gopenpgp/v2) available under [license](https://github.com/ProtonMail/gopenpgp/v2/blob/master/LICENSE)
|
* [gopenpgp](https://github.com/ProtonMail/gopenpgp/v2) available under [license](https://github.com/ProtonMail/gopenpgp/v2/blob/master/LICENSE)
|
||||||
* [goquery](https://github.com/PuerkitoBio/goquery) available under [license](https://github.com/PuerkitoBio/goquery/blob/master/LICENSE)
|
* [goquery](https://github.com/PuerkitoBio/goquery) available under [license](https://github.com/PuerkitoBio/goquery/blob/master/LICENSE)
|
||||||
* [ishell](https://github.com/abiosoft/ishell) available under [license](https://github.com/abiosoft/ishell/blob/master/LICENSE)
|
* [ishell](https://github.com/abiosoft/ishell) available under [license](https://github.com/abiosoft/ishell/blob/master/LICENSE)
|
||||||
* [readline](https://github.com/abiosoft/readline) available under [license](https://github.com/abiosoft/readline/blob/master/LICENSE)
|
|
||||||
* [go-singleinstance](https://github.com/allan-simon/go-singleinstance) available under [license](https://github.com/allan-simon/go-singleinstance/blob/master/LICENSE)
|
* [go-singleinstance](https://github.com/allan-simon/go-singleinstance) available under [license](https://github.com/allan-simon/go-singleinstance/blob/master/LICENSE)
|
||||||
* [logex](https://github.com/chzyer/logex) available under [license](https://github.com/chzyer/logex/blob/master/LICENSE)
|
* [juniper](https://github.com/bradenaw/juniper) available under [license](https://github.com/bradenaw/juniper/blob/master/LICENSE)
|
||||||
* [test](https://github.com/chzyer/test) available under [license](https://github.com/chzyer/test/blob/master/LICENSE)
|
|
||||||
* [godog](https://github.com/cucumber/godog) available under [license](https://github.com/cucumber/godog/blob/master/LICENSE)
|
* [godog](https://github.com/cucumber/godog) available under [license](https://github.com/cucumber/godog/blob/master/LICENSE)
|
||||||
* [messages-go](https://github.com/cucumber/messages-go/v16) available under [license](https://github.com/cucumber/messages-go/v16/blob/master/LICENSE)
|
* [messages-go](https://github.com/cucumber/messages-go/v16) available under [license](https://github.com/cucumber/messages-go/v16/blob/master/LICENSE)
|
||||||
|
* [docker-credential-helpers](https://github.com/docker/docker-credential-helpers) available under [license](https://github.com/docker/docker-credential-helpers/blob/master/LICENSE)
|
||||||
* [go-sysinfo](https://github.com/elastic/go-sysinfo) available under [license](https://github.com/elastic/go-sysinfo/blob/master/LICENSE)
|
* [go-sysinfo](https://github.com/elastic/go-sysinfo) available under [license](https://github.com/elastic/go-sysinfo/blob/master/LICENSE)
|
||||||
* [go-windows](https://github.com/elastic/go-windows) available under [license](https://github.com/elastic/go-windows/blob/master/LICENSE)
|
* [go-imap](https://github.com/emersion/go-imap) available under [license](https://github.com/emersion/go-imap/blob/master/LICENSE)
|
||||||
* [go-imap-appendlimit](https://github.com/emersion/go-imap-appendlimit) available under [license](https://github.com/emersion/go-imap-appendlimit/blob/master/LICENSE)
|
* [go-imap-id](https://github.com/emersion/go-imap-id) available under [license](https://github.com/emersion/go-imap-id/blob/master/LICENSE)
|
||||||
* [go-imap-move](https://github.com/emersion/go-imap-move) available under [license](https://github.com/emersion/go-imap-move/blob/master/LICENSE)
|
|
||||||
* [go-imap-quota](https://github.com/emersion/go-imap-quota) available under [license](https://github.com/emersion/go-imap-quota/blob/master/LICENSE)
|
|
||||||
* [go-imap-unselect](https://github.com/emersion/go-imap-unselect) available under [license](https://github.com/emersion/go-imap-unselect/blob/master/LICENSE)
|
|
||||||
* [go-message](https://github.com/emersion/go-message) available under [license](https://github.com/emersion/go-message/blob/master/LICENSE)
|
* [go-message](https://github.com/emersion/go-message) available under [license](https://github.com/emersion/go-message/blob/master/LICENSE)
|
||||||
* [go-sasl](https://github.com/emersion/go-sasl) available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE)
|
* [go-sasl](https://github.com/emersion/go-sasl) available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE)
|
||||||
* [go-smtp](https://github.com/emersion/go-smtp) available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE)
|
* [go-smtp](https://github.com/emersion/go-smtp) available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE)
|
||||||
* [go-textwrapper](https://github.com/emersion/go-textwrapper) available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE)
|
|
||||||
* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE)
|
* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE)
|
||||||
* [color](https://github.com/fatih/color) available under [license](https://github.com/fatih/color/blob/master/LICENSE)
|
* [color](https://github.com/fatih/color) available under [license](https://github.com/fatih/color/blob/master/LICENSE)
|
||||||
* [go-shlex](https://github.com/flynn-archive/go-shlex) available under [license](https://github.com/flynn-archive/go-shlex/blob/master/LICENSE)
|
|
||||||
* [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE)
|
* [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE)
|
||||||
* [resty](https://github.com/go-resty/resty/v2) available under [license](https://github.com/go-resty/resty/v2/blob/master/LICENSE)
|
* [resty](https://github.com/go-resty/resty/v2) available under [license](https://github.com/go-resty/resty/v2/blob/master/LICENSE)
|
||||||
* [dbus](https://github.com/godbus/dbus) available under [license](https://github.com/godbus/dbus/blob/master/LICENSE)
|
* [dbus](https://github.com/godbus/dbus) available under [license](https://github.com/godbus/dbus/blob/master/LICENSE)
|
||||||
@ -62,32 +50,91 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [uuid](https://github.com/google/uuid) available under [license](https://github.com/google/uuid/blob/master/LICENSE)
|
* [uuid](https://github.com/google/uuid) available under [license](https://github.com/google/uuid/blob/master/LICENSE)
|
||||||
* [go-multierror](https://github.com/hashicorp/go-multierror) available under [license](https://github.com/hashicorp/go-multierror/blob/master/LICENSE)
|
* [go-multierror](https://github.com/hashicorp/go-multierror) available under [license](https://github.com/hashicorp/go-multierror/blob/master/LICENSE)
|
||||||
* [html2text](https://github.com/jaytaylor/html2text) available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE)
|
* [html2text](https://github.com/jaytaylor/html2text) available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE)
|
||||||
|
* [go-locale](https://github.com/jeandeaual/go-locale) available under [license](https://github.com/jeandeaual/go-locale/blob/master/LICENSE)
|
||||||
* [go-keychain](https://github.com/keybase/go-keychain) available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
|
* [go-keychain](https://github.com/keybase/go-keychain) available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
|
||||||
* [text](https://github.com/kr/text) available under [license](https://github.com/kr/text/blob/master/LICENSE)
|
|
||||||
* [aurora](https://github.com/logrusorgru/aurora) available under [license](https://github.com/logrusorgru/aurora/blob/master/LICENSE)
|
|
||||||
* [go-runewidth](https://github.com/mattn/go-runewidth) available under [license](https://github.com/mattn/go-runewidth/blob/master/LICENSE)
|
|
||||||
* [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
|
* [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
|
||||||
* [pretty](https://github.com/niemeyer/pretty) available under [license](https://github.com/niemeyer/pretty/blob/master/LICENSE)
|
* [memory](https://github.com/pbnjay/memory) available under [license](https://github.com/pbnjay/memory/blob/master/LICENSE)
|
||||||
* [jsondiff](https://github.com/nsf/jsondiff) available under [license](https://github.com/nsf/jsondiff/blob/master/LICENSE)
|
|
||||||
* [tablewriter](https://github.com/olekukonko/tablewriter) available under [license](https://github.com/olekukonko/tablewriter/blob/master/LICENSE)
|
|
||||||
* [errors](https://github.com/pkg/errors) available under [license](https://github.com/pkg/errors/blob/master/LICENSE)
|
* [errors](https://github.com/pkg/errors) available under [license](https://github.com/pkg/errors/blob/master/LICENSE)
|
||||||
* [procfs](https://github.com/prometheus/procfs) available under [license](https://github.com/prometheus/procfs/blob/master/LICENSE)
|
* [profile](https://github.com/pkg/profile) available under [license](https://github.com/pkg/profile/blob/master/LICENSE)
|
||||||
* [du](https://github.com/ricochet2200/go-disk-usage/du) available under [license](https://github.com/ricochet2200/go-disk-usage/du/blob/master/LICENSE)
|
|
||||||
* [logrus](https://github.com/sirupsen/logrus) available under [license](https://github.com/sirupsen/logrus/blob/master/LICENSE)
|
* [logrus](https://github.com/sirupsen/logrus) available under [license](https://github.com/sirupsen/logrus/blob/master/LICENSE)
|
||||||
* [bom](https://github.com/ssor/bom) available under [license](https://github.com/ssor/bom/blob/master/LICENSE)
|
|
||||||
* [testify](https://github.com/stretchr/testify) available under [license](https://github.com/stretchr/testify/blob/master/LICENSE)
|
* [testify](https://github.com/stretchr/testify) available under [license](https://github.com/stretchr/testify/blob/master/LICENSE)
|
||||||
* [qt](https://github.com/therecipe/qt) available under [license](https://github.com/therecipe/qt/blob/master/LICENSE)
|
|
||||||
* [cli](https://github.com/urfave/cli/v2) available under [license](https://github.com/urfave/cli/v2/blob/master/LICENSE)
|
* [cli](https://github.com/urfave/cli/v2) available under [license](https://github.com/urfave/cli/v2/blob/master/LICENSE)
|
||||||
* [msgpack](https://github.com/vmihailenco/msgpack/v5) available under [license](https://github.com/vmihailenco/msgpack/v5/blob/master/LICENSE)
|
* [msgpack](https://github.com/vmihailenco/msgpack/v5) available under [license](https://github.com/vmihailenco/msgpack/v5/blob/master/LICENSE)
|
||||||
* [bbolt](https://go.etcd.io/bbolt) available under [license](https://github.com/etcd-io/bbolt/blob/master/LICENSE)
|
* [goleak](https://go.uber.org/goleak) available under [license](https://pkg.go.dev/go.uber.org/goleak?tab=licenses)
|
||||||
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
|
* [exp](https://golang.org/x/exp) available under [license](https://cs.opensource.google/go/x/exp/+/master:LICENSE)
|
||||||
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
|
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
|
||||||
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
|
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
|
||||||
* [text](https://golang.org/x/text) available under [license](https://cs.opensource.google/go/x/text/+/master:LICENSE)
|
* [text](https://golang.org/x/text) available under [license](https://cs.opensource.google/go/x/text/+/master:LICENSE)
|
||||||
|
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
|
||||||
|
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
|
||||||
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
|
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
|
||||||
* [docker-credential-helpers](https://github.com/ProtonMail/docker-credential-helpers) available under [license](https://github.com/ProtonMail/docker-credential-helpers/blob/master/LICENSE)
|
|
||||||
* [go-imap](https://github.com/ProtonMail/go-imap) available under [license](https://github.com/ProtonMail/go-imap/blob/master/LICENSE)
|
|
||||||
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
|
|
||||||
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
|
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
|
||||||
|
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
|
||||||
|
* [go-mime](https://github.com/ProtonMail/go-mime) available under [license](https://github.com/ProtonMail/go-mime/blob/master/LICENSE)
|
||||||
|
* [go-srp](https://github.com/ProtonMail/go-srp) available under [license](https://github.com/ProtonMail/go-srp/blob/master/LICENSE)
|
||||||
|
* [readline](https://github.com/abiosoft/readline) available under [license](https://github.com/abiosoft/readline/blob/master/LICENSE)
|
||||||
|
* [cascadia](https://github.com/andybalholm/cascadia) available under [license](https://github.com/andybalholm/cascadia/blob/master/LICENSE)
|
||||||
|
* [sonic](https://github.com/bytedance/sonic) available under [license](https://github.com/bytedance/sonic/blob/master/LICENSE)
|
||||||
|
* [base64x](https://github.com/chenzhuoyu/base64x) available under [license](https://github.com/chenzhuoyu/base64x/blob/master/LICENSE)
|
||||||
|
* [test](https://github.com/chzyer/test) available under [license](https://github.com/chzyer/test/blob/master/LICENSE)
|
||||||
|
* [circl](https://github.com/cloudflare/circl) available under [license](https://github.com/cloudflare/circl/blob/master/LICENSE)
|
||||||
|
* [go-md2man](https://github.com/cpuguy83/go-md2man/v2) available under [license](https://github.com/cpuguy83/go-md2man/v2/blob/master/LICENSE)
|
||||||
|
* [saferith](https://github.com/cronokirby/saferith) available under [license](https://github.com/cronokirby/saferith/blob/master/LICENSE)
|
||||||
|
* [gherkin-go](https://github.com/cucumber/gherkin-go/v19) available under [license](https://github.com/cucumber/gherkin-go/v19/blob/master/LICENSE)
|
||||||
|
* [wincred](https://github.com/danieljoos/wincred) available under [license](https://github.com/danieljoos/wincred/blob/master/LICENSE)
|
||||||
|
* [go-spew](https://github.com/davecgh/go-spew) available under [license](https://github.com/davecgh/go-spew/blob/master/LICENSE)
|
||||||
|
* [go-windows](https://github.com/elastic/go-windows) available under [license](https://github.com/elastic/go-windows/blob/master/LICENSE)
|
||||||
|
* [go-textwrapper](https://github.com/emersion/go-textwrapper) available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE)
|
||||||
|
* [fgprof](https://github.com/felixge/fgprof) available under [license](https://github.com/felixge/fgprof/blob/master/LICENSE)
|
||||||
|
* [go-shlex](https://github.com/flynn-archive/go-shlex) available under [license](https://github.com/flynn-archive/go-shlex/blob/master/LICENSE)
|
||||||
|
* [mimetype](https://github.com/gabriel-vasile/mimetype) available under [license](https://github.com/gabriel-vasile/mimetype/blob/master/LICENSE)
|
||||||
|
* [sse](https://github.com/gin-contrib/sse) available under [license](https://github.com/gin-contrib/sse/blob/master/LICENSE)
|
||||||
|
* [gin](https://github.com/gin-gonic/gin) available under [license](https://github.com/gin-gonic/gin/blob/master/LICENSE)
|
||||||
|
* [locales](https://github.com/go-playground/locales) available under [license](https://github.com/go-playground/locales/blob/master/LICENSE)
|
||||||
|
* [universal-translator](https://github.com/go-playground/universal-translator) available under [license](https://github.com/go-playground/universal-translator/blob/master/LICENSE)
|
||||||
|
* [validator](https://github.com/go-playground/validator/v10) available under [license](https://github.com/go-playground/validator/v10/blob/master/LICENSE)
|
||||||
|
* [go-json](https://github.com/goccy/go-json) available under [license](https://github.com/goccy/go-json/blob/master/LICENSE)
|
||||||
|
* [uuid](https://github.com/gofrs/uuid) available under [license](https://github.com/gofrs/uuid/blob/master/LICENSE)
|
||||||
|
* [protobuf](https://github.com/golang/protobuf) available under [license](https://github.com/golang/protobuf/blob/master/LICENSE)
|
||||||
|
* [pprof](https://github.com/google/pprof) available under [license](https://github.com/google/pprof/blob/master/LICENSE)
|
||||||
|
* [errwrap](https://github.com/hashicorp/errwrap) available under [license](https://github.com/hashicorp/errwrap/blob/master/LICENSE)
|
||||||
|
* [go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) available under [license](https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE)
|
||||||
|
* [go-memdb](https://github.com/hashicorp/go-memdb) available under [license](https://github.com/hashicorp/go-memdb/blob/master/LICENSE)
|
||||||
|
* [golang-lru](https://github.com/hashicorp/golang-lru) available under [license](https://github.com/hashicorp/golang-lru/blob/master/LICENSE)
|
||||||
|
* [multierror](https://github.com/joeshaw/multierror) available under [license](https://github.com/joeshaw/multierror/blob/master/LICENSE)
|
||||||
|
* [go](https://github.com/json-iterator/go) available under [license](https://github.com/json-iterator/go/blob/master/LICENSE)
|
||||||
|
* [cpuid](https://github.com/klauspost/cpuid/v2) available under [license](https://github.com/klauspost/cpuid/v2/blob/master/LICENSE)
|
||||||
|
* [go-urn](https://github.com/leodido/go-urn) available under [license](https://github.com/leodido/go-urn/blob/master/LICENSE)
|
||||||
|
* [go-colorable](https://github.com/mattn/go-colorable) available under [license](https://github.com/mattn/go-colorable/blob/master/LICENSE)
|
||||||
|
* [go-isatty](https://github.com/mattn/go-isatty) available under [license](https://github.com/mattn/go-isatty/blob/master/LICENSE)
|
||||||
|
* [go-runewidth](https://github.com/mattn/go-runewidth) available under [license](https://github.com/mattn/go-runewidth/blob/master/LICENSE)
|
||||||
|
* [go-sqlite3](https://github.com/mattn/go-sqlite3) available under [license](https://github.com/mattn/go-sqlite3/blob/master/LICENSE)
|
||||||
|
* [concurrent](https://github.com/modern-go/concurrent) available under [license](https://github.com/modern-go/concurrent/blob/master/LICENSE)
|
||||||
|
* [reflect2](https://github.com/modern-go/reflect2) available under [license](https://github.com/modern-go/reflect2/blob/master/LICENSE)
|
||||||
|
* [tablewriter](https://github.com/olekukonko/tablewriter) available under [license](https://github.com/olekukonko/tablewriter/blob/master/LICENSE)
|
||||||
|
* [go-toml](https://github.com/pelletier/go-toml/v2) available under [license](https://github.com/pelletier/go-toml/v2/blob/master/LICENSE)
|
||||||
|
* [lz4](https://github.com/pierrec/lz4/v4) available under [license](https://github.com/pierrec/lz4/v4/blob/master/LICENSE)
|
||||||
|
* [go-difflib](https://github.com/pmezard/go-difflib) available under [license](https://github.com/pmezard/go-difflib/blob/master/LICENSE)
|
||||||
|
* [procfs](https://github.com/prometheus/procfs) available under [license](https://github.com/prometheus/procfs/blob/master/LICENSE)
|
||||||
|
* [uniseg](https://github.com/rivo/uniseg) available under [license](https://github.com/rivo/uniseg/blob/master/LICENSE)
|
||||||
|
* [blackfriday](https://github.com/russross/blackfriday/v2) available under [license](https://github.com/russross/blackfriday/v2/blob/master/LICENSE)
|
||||||
|
* [pflag](https://github.com/spf13/pflag) available under [license](https://github.com/spf13/pflag/blob/master/LICENSE)
|
||||||
|
* [bom](https://github.com/ssor/bom) available under [license](https://github.com/ssor/bom/blob/master/LICENSE)
|
||||||
|
* [golang-asm](https://github.com/twitchyliquid64/golang-asm) available under [license](https://github.com/twitchyliquid64/golang-asm/blob/master/LICENSE)
|
||||||
|
* [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE)
|
||||||
|
* [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE)
|
||||||
|
* [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE)
|
||||||
|
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json)
|
||||||
|
* [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE)
|
||||||
|
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
|
||||||
|
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)
|
||||||
|
* [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE)
|
||||||
|
* [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE)
|
||||||
|
* [genproto](https://google.golang.org/genproto) available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses)
|
||||||
|
* [yaml](https://gopkg.in/yaml.v3) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)
|
||||||
|
* [docker-credential-helpers](https://github.com/ProtonMail/docker-credential-helpers) available under [license](https://github.com/ProtonMail/docker-credential-helpers/blob/master/LICENSE)
|
||||||
|
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
|
||||||
|
* [go-smtp](https://github.com/ProtonMail/go-smtp) available under [license](https://github.com/ProtonMail/go-smtp/blob/master/LICENSE)
|
||||||
|
* [resty](https://github.com/LBeernaertProton/resty/v2) available under [license](https://github.com/LBeernaertProton/resty/v2/blob/master/LICENSE)
|
||||||
* [go-keychain](https://github.com/cuthix/go-keychain) available under [license](https://github.com/cuthix/go-keychain/blob/master/LICENSE)
|
* [go-keychain](https://github.com/cuthix/go-keychain) available under [license](https://github.com/cuthix/go-keychain/blob/master/LICENSE)
|
||||||
<!-- END AUTOGEN -->
|
<!-- END AUTOGEN -->
|
||||||
|
|||||||
1150
Changelog.md
407
Makefile
@ -5,165 +5,187 @@ export GO111MODULE=on
|
|||||||
GOOS:=$(shell go env GOOS)
|
GOOS:=$(shell go env GOOS)
|
||||||
TARGET_CMD?=Desktop-Bridge
|
TARGET_CMD?=Desktop-Bridge
|
||||||
TARGET_OS?=${GOOS}
|
TARGET_OS?=${GOOS}
|
||||||
|
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
.PHONY: build build-nogui build-launcher versioner hasher
|
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
||||||
|
|
||||||
# Keep version hardcoded so app build works also without Git repository.
|
# Keep version hardcoded so app build works also without Git repository.
|
||||||
BRIDGE_APP_VERSION?=2.2.0+git
|
BRIDGE_APP_VERSION?=3.8.1+git
|
||||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||||
SRC_ICO:=logo.ico
|
APP_FULL_NAME:=Proton Mail Bridge
|
||||||
|
APP_VENDOR:=Proton AG
|
||||||
|
SRC_ICO:=bridge.ico
|
||||||
SRC_ICNS:=Bridge.icns
|
SRC_ICNS:=Bridge.icns
|
||||||
SRC_SVG:=logo.svg
|
SRC_SVG:=bridge.svg
|
||||||
EXE_NAME:=proton-bridge
|
EXE_NAME:=proton-bridge
|
||||||
CONFIGNAME:=bridge
|
REVISION:=$(shell ./utils/get_revision.sh)
|
||||||
REVISION:=$(shell git rev-parse --short=10 HEAD)
|
TAG:=$(shell ./utils/get_revision.sh tag)
|
||||||
BUILD_TIME:=$(shell date +%FT%T%z)
|
BUILD_TIME:=$(shell date +%FT%T%z)
|
||||||
|
MACOS_MIN_VERSION_ARM64=11.0
|
||||||
|
MACOS_MIN_VERSION_AMD64=10.15
|
||||||
|
BUILD_ENV?=dev
|
||||||
|
|
||||||
BUILD_FLAGS:=-tags='${BUILD_TAGS}'
|
BUILD_FLAGS:=-tags='${BUILD_TAGS}'
|
||||||
BUILD_FLAGS_LAUNCHER:=${BUILD_FLAGS}
|
BUILD_FLAGS_LAUNCHER:=${BUILD_FLAGS}
|
||||||
BUILD_FLAGS_GUI:=-tags='${BUILD_TAGS} build_qt'
|
GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/v3/internal/constants., Version=${APP_VERSION} Revision=${REVISION} Tag=${TAG} BuildTime=${BUILD_TIME})
|
||||||
GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/internal/constants.,Version=${APP_VERSION} Revision=${REVISION} BuildTime=${BUILD_TIME})
|
GO_LDFLAGS+=-X "github.com/ProtonMail/proton-bridge/v3/internal/constants.FullAppName=${APP_FULL_NAME}"
|
||||||
ifneq "${BUILD_LDFLAGS}" ""
|
|
||||||
GO_LDFLAGS+=${BUILD_LDFLAGS}
|
ifneq "${DSN_SENTRY}" ""
|
||||||
|
GO_LDFLAGS+=-X github.com/ProtonMail/proton-bridge/v3/internal/constants.DSNSentry=${DSN_SENTRY}
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifneq "${BUILD_ENV}" ""
|
||||||
|
GO_LDFLAGS+=-X github.com/ProtonMail/proton-bridge/v3/internal/constants.BuildEnv=${BUILD_ENV}
|
||||||
|
endif
|
||||||
|
|
||||||
GO_LDFLAGS_LAUNCHER:=${GO_LDFLAGS}
|
GO_LDFLAGS_LAUNCHER:=${GO_LDFLAGS}
|
||||||
ifeq "${TARGET_OS}" "windows"
|
ifeq "${TARGET_OS}" "windows"
|
||||||
GO_LDFLAGS_LAUNCHER+=-H=windowsgui
|
#GO_LDFLAGS+=-H=windowsgui # Disabled so we can inspect trace logs from the bridge for debugging.
|
||||||
|
GO_LDFLAGS_LAUNCHER+=-H=windowsgui # Having this flag prevent a temporary cmd.exe window from popping when starting the application on Windows 11.
|
||||||
endif
|
endif
|
||||||
|
|
||||||
BUILD_FLAGS+=-ldflags '${GO_LDFLAGS}'
|
BUILD_FLAGS+=-ldflags '${GO_LDFLAGS}'
|
||||||
BUILD_FLAGS_GUI+=-ldflags '${GO_LDFLAGS}'
|
|
||||||
BUILD_FLAGS_LAUNCHER+=-ldflags '${GO_LDFLAGS_LAUNCHER}'
|
BUILD_FLAGS_LAUNCHER+=-ldflags '${GO_LDFLAGS_LAUNCHER}'
|
||||||
|
|
||||||
DEPLOY_DIR:=cmd/${TARGET_CMD}/deploy
|
DEPLOY_DIR:=cmd/${TARGET_CMD}/deploy
|
||||||
ICO_FILES:=
|
|
||||||
DIRNAME:=$(shell basename ${CURDIR})
|
DIRNAME:=$(shell basename ${CURDIR})
|
||||||
EXE:=${EXE_NAME}
|
|
||||||
EXE_QT:=${DIRNAME}
|
LAUNCHER_EXE:=proton-bridge
|
||||||
|
BRIDGE_EXE=bridge
|
||||||
|
BRIDGE_GUI_EXE_NAME=bridge-gui
|
||||||
|
BRIDGE_GUI_EXE=${BRIDGE_GUI_EXE_NAME}
|
||||||
|
LAUNCHER_PATH:=cmd/launcher
|
||||||
|
|
||||||
ifeq "${TARGET_OS}" "windows"
|
ifeq "${TARGET_OS}" "windows"
|
||||||
EXE:=${EXE}.exe
|
BRIDGE_EXE:=${BRIDGE_EXE}.exe
|
||||||
EXE_QT:=${EXE_QT}.exe
|
BRIDGE_GUI_EXE:=${BRIDGE_GUI_EXE}.exe
|
||||||
RESOURCE_FILE:=resource.syso
|
LAUNCHER_EXE:=${LAUNCHER_EXE}.exe
|
||||||
|
RESOURCE_FILE:=resource.syso
|
||||||
endif
|
endif
|
||||||
ifeq "${TARGET_OS}" "darwin"
|
ifeq "${TARGET_OS}" "darwin"
|
||||||
DARWINAPP_CONTENTS:=${DEPLOY_DIR}/darwin/${EXE}.app/Contents
|
BRIDGE_EXE_NAME:=${BRIDGE_EXE}
|
||||||
EXE:=${EXE}.app
|
BRIDGE_EXE:=${BRIDGE_EXE}.app
|
||||||
EXE_QT:=${EXE_QT}.app
|
BRIDGE_GUI_EXE:=${BRIDGE_GUI_EXE}.app
|
||||||
EXE_BINARY_DARWIN:=/Contents/MacOS/${EXE_NAME}
|
EXE_BINARY_DARWIN:=Contents/MacOS/${BRIDGE_GUI_EXE_NAME}
|
||||||
|
EXE_TARGET_DARWIN:=${DEPLOY_DIR}/${TARGET_OS}/${LAUNCHER_EXE}.app
|
||||||
|
DARWINAPP_CONTENTS:=${EXE_TARGET_DARWIN}/Contents
|
||||||
endif
|
endif
|
||||||
EXE_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE}
|
EXE_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${BRIDGE_EXE}
|
||||||
EXE_QT_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE_QT}
|
EXE_GUI_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${BRIDGE_GUI_EXE}
|
||||||
|
|
||||||
TGZ_TARGET:=bridge_${TARGET_OS}_${REVISION}.tgz
|
TGZ_TARGET:=bridge_${TARGET_OS}_${REVISION}.tgz
|
||||||
|
|
||||||
ifdef QT_API
|
ifdef QT_API
|
||||||
VENDOR_TARGET:=prepare-vendor update-qt-docs
|
VENDOR_TARGET:=prepare-vendor update-qt-docs
|
||||||
else
|
else
|
||||||
VENDOR_TARGET=update-vendor
|
VENDOR_TARGET=update-vendor
|
||||||
endif
|
endif
|
||||||
|
|
||||||
build: ${TGZ_TARGET}
|
build: build-gui
|
||||||
|
|
||||||
build-nogui: gofiles
|
build-gui: ${TGZ_TARGET}
|
||||||
go build ${BUILD_FLAGS} -o ${EXE_NAME} cmd/${TARGET_CMD}/main.go
|
|
||||||
|
build-nogui: ${EXE_NAME} build-launcher
|
||||||
|
ifeq "${TARGET_OS}" "darwin"
|
||||||
|
mv ${BRIDGE_EXE} ${BRIDGE_EXE_NAME}
|
||||||
|
endif
|
||||||
|
|
||||||
|
go-build=go build $(1) -o $(2) $(3)
|
||||||
|
go-build-finalize=${go-build}
|
||||||
|
ifeq "${GOOS}-$(shell uname -m)" "darwin-arm64"
|
||||||
|
go-build-finalize= \
|
||||||
|
MACOSX_DEPLOYMENT_TARGET=${MACOS_MIN_VERSION_ARM64} CGO_ENABLED=1 CGO_CFLAGS="-mmacosx-version-min=${MACOS_MIN_VERSION_ARM64}" GOARCH=arm64 $(call go-build,$(1),$(2)_arm,$(3)) && \
|
||||||
|
MACOSX_DEPLOYMENT_TARGET=${MACOS_MIN_VERSION_AMD64} CGO_ENABLED=1 CGO_CFLAGS="-mmacosx-version-min=${MACOS_MIN_VERSION_AMD64}" GOARCH=amd64 $(call go-build,$(1),$(2)_amd,$(3)) && \
|
||||||
|
lipo -create -output $(2) $(2)_arm $(2)_amd && rm -f $(2)_arm $(2)_amd
|
||||||
|
endif
|
||||||
|
|
||||||
ifeq "${GOOS}" "windows"
|
ifeq "${GOOS}" "windows"
|
||||||
PRERESOURCECMD:=cp ./resource.syso ./cmd/launcher/resource.syso
|
go-build-finalize= \
|
||||||
POSTRESOURCECMD:=rm -f ./cmd/launcher/resource.syso
|
$(if $(4),powershell Copy-Item ${ROOT_DIR}/${RESOURCE_FILE} ${4} &&,) \
|
||||||
|
$(call go-build,$(1),$(2),$(3)) \
|
||||||
|
$(if $(4), && powershell Remove-Item ${4} -Force,)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
${EXE_NAME}: gofiles ${RESOURCE_FILE}
|
||||||
|
$(call go-build-finalize,${BUILD_FLAGS},"${LAUNCHER_EXE}","./cmd/${TARGET_CMD}/","${ROOT_DIR}/cmd/${TARGET_CMD}/${RESOURCE_FILE}")
|
||||||
|
mv ${LAUNCHER_EXE} ${BRIDGE_EXE}
|
||||||
|
|
||||||
build-launcher: ${RESOURCE_FILE}
|
build-launcher: ${RESOURCE_FILE}
|
||||||
${PRERESOURCECMD}
|
$(call go-build-finalize,${BUILD_FLAGS_LAUNCHER},"${LAUNCHER_EXE}","${ROOT_DIR}/${LAUNCHER_PATH}/","${ROOT_DIR}/${LAUNCHER_PATH}/${RESOURCE_FILE}")
|
||||||
go build ${BUILD_FLAGS_LAUNCHER} -o launcher-${EXE} ./cmd/launcher/
|
|
||||||
${POSTRESOURCECMD}
|
|
||||||
|
|
||||||
versioner:
|
versioner:
|
||||||
go build ${BUILD_FLAGS} -o versioner utils/versioner/main.go
|
go build ${BUILD_FLAGS} -o versioner utils/versioner/main.go
|
||||||
|
|
||||||
|
vault-editor:
|
||||||
|
$(call go-build-finalize,"-tags=debug","vault-editor","./utils/vault-editor/main.go")
|
||||||
|
|
||||||
hasher:
|
hasher:
|
||||||
go build -o hasher utils/hasher/main.go
|
go build -o hasher utils/hasher/main.go
|
||||||
|
|
||||||
${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS}
|
${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS}
|
||||||
rm -f $@
|
rm -f $@
|
||||||
cd ${DEPLOY_DIR}/${TARGET_OS} && tar -czvf ../../../../$@ .
|
tar -czvf $@ -C ${DEPLOY_DIR}/${TARGET_OS} .
|
||||||
|
|
||||||
${DEPLOY_DIR}/linux: ${EXE_TARGET}
|
${DEPLOY_DIR}/linux: ${EXE_TARGET} build-launcher
|
||||||
cp -pf ./internal/frontend/share/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
|
cp -pf ./dist/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
|
||||||
cp -pf ./LICENSE ${DEPLOY_DIR}/linux/
|
cp -pf ./LICENSE ${DEPLOY_DIR}/linux/
|
||||||
cp -pf ./Changelog.md ${DEPLOY_DIR}/linux/
|
cp -pf ./Changelog.md ${DEPLOY_DIR}/linux/
|
||||||
cp -pf ./dist/${EXE_NAME}.desktop ${DEPLOY_DIR}/linux/
|
cp -pf ./dist/${EXE_NAME}.desktop ${DEPLOY_DIR}/linux/
|
||||||
|
mv ${LAUNCHER_EXE} ${DEPLOY_DIR}/linux/
|
||||||
|
|
||||||
${DEPLOY_DIR}/darwin: ${EXE_TARGET}
|
${DEPLOY_DIR}/darwin: ${EXE_TARGET} build-launcher
|
||||||
if [ "${DIRNAME}" != "${EXE_NAME}" ]; then \
|
mv ${EXE_GUI_TARGET} ${EXE_TARGET_DARWIN}
|
||||||
mv ${EXE_TARGET}/Contents/MacOS/{${DIRNAME},${EXE_NAME}}; \
|
mv ${EXE_TARGET} ${DARWINAPP_CONTENTS}/MacOS/${BRIDGE_EXE_NAME}
|
||||||
perl -i -pe"s/>${DIRNAME}/>${EXE_NAME}/g" ${EXE_TARGET}/Contents/Info.plist; \
|
perl -i -pe"s/>${BRIDGE_GUI_EXE_NAME}/>${LAUNCHER_EXE}/g" ${DARWINAPP_CONTENTS}/Info.plist
|
||||||
fi
|
cp ./dist/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${SRC_ICNS}
|
||||||
cp ./internal/frontend/share/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${SRC_ICNS}
|
|
||||||
cp LICENSE ${DARWINAPP_CONTENTS}/Resources/
|
cp LICENSE ${DARWINAPP_CONTENTS}/Resources/
|
||||||
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngine.framework"
|
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngine.framework"
|
||||||
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebView.framework"
|
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebView.framework"
|
||||||
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngineCore.framework"
|
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngineCore.framework"
|
||||||
./utils/remove_non_relative_links_darwin.sh "${EXE_TARGET}${EXE_BINARY_DARWIN}"
|
mv ${LAUNCHER_EXE} ${DARWINAPP_CONTENTS}/MacOS/${LAUNCHER_EXE}
|
||||||
|
./utils/remove_non_relative_links_darwin.sh "${EXE_TARGET_DARWIN}/${EXE_BINARY_DARWIN}"
|
||||||
|
|
||||||
${DEPLOY_DIR}/windows: ${EXE_TARGET}
|
${DEPLOY_DIR}/windows: ${EXE_TARGET} build-launcher
|
||||||
cp ./internal/frontend/share/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
|
cp ./dist/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
|
||||||
cp LICENSE ${DEPLOY_DIR}/windows/
|
cp LICENSE ${DEPLOY_DIR}/windows/LICENSE.txt
|
||||||
|
mv ${LAUNCHER_EXE} ${DEPLOY_DIR}/windows/$(notdir ${LAUNCHER_EXE})
|
||||||
QT_BUILD_TARGET:=build desktop
|
# plugins are installed in a plugins folder while needs to be near the exe
|
||||||
ifneq "${GOOS}" "${TARGET_OS}"
|
cp -rf ${DEPLOY_DIR}/windows/plugins/* ${DEPLOY_DIR}/windows/.
|
||||||
ifeq "${TARGET_OS}" "windows"
|
rm -rf ${DEPLOY_DIR}/windows/plugins
|
||||||
QT_BUILD_TARGET:=-docker build windows_64_shared
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
${EXE_TARGET}: check-has-go gofiles ${RESOURCE_FILE} ${VENDOR_TARGET}
|
|
||||||
rm -rf deploy ${TARGET_OS} ${DEPLOY_DIR}
|
|
||||||
cp cmd/${TARGET_CMD}/main.go .
|
|
||||||
qtdeploy ${BUILD_FLAGS_GUI} ${QT_BUILD_TARGET}
|
|
||||||
mv deploy cmd/${TARGET_CMD}
|
|
||||||
if [ "${EXE_QT_TARGET}" != "${EXE_TARGET}" ]; then mv ${EXE_QT_TARGET} ${EXE_TARGET}; fi
|
|
||||||
rm -rf ${TARGET_OS} main.go
|
|
||||||
|
|
||||||
|
${EXE_TARGET}: check-build-essentials ${EXE_NAME}
|
||||||
|
cd internal/frontend/bridge-gui/bridge-gui && \
|
||||||
|
BRIDGE_APP_FULL_NAME="${APP_FULL_NAME}" \
|
||||||
|
BRIDGE_VENDOR="${APP_VENDOR}" \
|
||||||
|
BRIDGE_APP_VERSION=${APP_VERSION} \
|
||||||
|
BRIDGE_REVISION=${REVISION} \
|
||||||
|
BRIDGE_TAG=${TAG} \
|
||||||
|
BRIDGE_DSN_SENTRY=${DSN_SENTRY} \
|
||||||
|
BRIDGE_BUILD_TIME=${BUILD_TIME} \
|
||||||
|
BRIDGE_GUI_BUILD_CONFIG=Release \
|
||||||
|
BRIDGE_BUILD_ENV=${BUILD_ENV} \
|
||||||
|
BRIDGE_INSTALL_PATH=${ROOT_DIR}/${DEPLOY_DIR}/${GOOS} \
|
||||||
|
./build.sh install
|
||||||
|
mv "${ROOT_DIR}/${BRIDGE_EXE}" "$(ROOT_DIR)/${EXE_TARGET}"
|
||||||
|
|
||||||
WINDRES_YEAR:=$(shell date +%Y)
|
WINDRES_YEAR:=$(shell date +%Y)
|
||||||
APP_VERSION_COMMA:=$(shell echo "${APP_VERSION}" | sed -e 's/[^0-9,.]*//g' -e 's/\./,/g')
|
APP_VERSION_COMMA:=$(shell echo "${APP_VERSION}" | sed -e 's/[^0-9,.]*//g' -e 's/\./,/g')
|
||||||
resource.syso: ./internal/frontend/share/info.rc ./internal/frontend/share/${SRC_ICO} .FORCE
|
${RESOURCE_FILE}: ./dist/info.rc ./dist/${SRC_ICO} .FORCE
|
||||||
rm -f ./*.syso
|
rm -f ./*.syso
|
||||||
windres --target=pe-x86-64 -I ./internal/frontend/share/ -D ICO_FILE=${SRC_ICO} -D EXE_NAME="${EXE_NAME}" -D FILE_VERSION="${APP_VERSION}" -D ORIGINAL_FILE_NAME="${EXE}" -D PRODUCT_VERSION="${APP_VERSION}" -D FILE_VERSION_COMMA=${APP_VERSION_COMMA} -D YEAR=${WINDRES_YEAR} -o $@ $<
|
windres --target=pe-x86-64 \
|
||||||
|
-I ./internal/frontend/share/ \
|
||||||
## Rules for therecipe/qt
|
-D ICO_FILE=${SRC_ICO} \
|
||||||
.PHONY: prepare-vendor update-vendor update-qt-docs
|
-D EXE_NAME="${EXE_NAME}" \
|
||||||
THERECIPE_ENV:=github.com/therecipe/env_${TARGET_OS}_amd64_513
|
-D FILE_VERSION="${APP_VERSION}" \
|
||||||
|
-D ORIGINAL_FILE_NAME="${EXE}" \
|
||||||
# vendor folder will be deleted by gomod hence we cache the big repo
|
-D PRODUCT_VERSION="${APP_VERSION}" \
|
||||||
# therecipe/env in order to download it only once
|
-D FILE_VERSION_COMMA=${APP_VERSION_COMMA} \
|
||||||
vendor-cache/${THERECIPE_ENV}:
|
-D YEAR=${WINDRES_YEAR} \
|
||||||
git clone https://${THERECIPE_ENV}.git vendor-cache/${THERECIPE_ENV}
|
-o ./${RESOURCE_FILE} $<
|
||||||
if [ "${TARGET_OS}" == "darwin" ]; then cp -f "./utils/QTBUG-88600/libqcocoa.dylib" "./vendor-cache/${THERECIPE_ENV}/5.13.0/clang_64/plugins/platforms/"; fi;
|
|
||||||
|
|
||||||
# The command used to make symlinks is different on windows.
|
|
||||||
# So if the GOOS is windows and we aren't crossbuilding (in which case the host os would still be *nix)
|
|
||||||
# we need to change the LINKCMD to something windowsy.
|
|
||||||
LINKCMD:=ln -sf ${CURDIR}/vendor-cache/${THERECIPE_ENV} vendor/${THERECIPE_ENV}
|
|
||||||
ifeq "${GOOS}" "windows"
|
|
||||||
WINDIR:=$(subst /c/,c:\\,${CURDIR})/vendor-cache/${THERECIPE_ENV}
|
|
||||||
LINKCMD:=cmd //c 'mklink $(subst /,\,vendor\${THERECIPE_ENV} ${WINDIR})'
|
|
||||||
endif
|
|
||||||
|
|
||||||
prepare-vendor:
|
|
||||||
go install -v -tags=no_env github.com/therecipe/qt/cmd/...
|
|
||||||
go mod vendor
|
|
||||||
|
|
||||||
# update-vendor is PHONY because we need to make sure that we always have updated vendor
|
|
||||||
update-vendor: vendor-cache/${THERECIPE_ENV} prepare-vendor
|
|
||||||
${LINKCMD}
|
|
||||||
|
|
||||||
update-qt-docs:
|
|
||||||
go get github.com/therecipe/qt/internal/binding/files/docs/$(QT_API)
|
|
||||||
|
|
||||||
## Dev dependencies
|
## Dev dependencies
|
||||||
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
|
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
|
||||||
LINTVER:="v1.39.0"
|
LINTVER:="v1.52.2"
|
||||||
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
||||||
|
|
||||||
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
|
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
|
||||||
@ -177,16 +199,29 @@ install-linter: check-has-go
|
|||||||
curl -sfL $(LINTSRC) | sh -s -- -b $(shell go env GOPATH)/bin $(LINTVER)
|
curl -sfL $(LINTSRC) | sh -s -- -b $(shell go env GOPATH)/bin $(LINTVER)
|
||||||
|
|
||||||
install-go-mod-outdated:
|
install-go-mod-outdated:
|
||||||
which go-mod-outdated || go get -u github.com/psampaz/go-mod-outdated
|
which go-mod-outdated || go install github.com/psampaz/go-mod-outdated@latest
|
||||||
|
|
||||||
install-git-hooks:
|
install-git-hooks:
|
||||||
cp utils/githooks/* .git/hooks/
|
cp utils/githooks/* .git/hooks/
|
||||||
chmod +x .git/hooks/*
|
chmod +x .git/hooks/*
|
||||||
|
|
||||||
## Checks, mocks and docs
|
## Checks, mocks and docs
|
||||||
.PHONY: check-has-go add-license change-copyright-year test bench coverage mocks lint-license lint-golang lint updates doc release-notes
|
.PHONY: check-has-go check-build-essentials add-license change-copyright-year test bench coverage mocks lint-license lint-golang lint updates doc release-notes
|
||||||
check-has-go:
|
check-has-go:
|
||||||
@which go || (echo "Install Go-lang!" && exit 1)
|
@which go || (echo "Install Go-lang!" && exit 1)
|
||||||
|
go version
|
||||||
|
|
||||||
|
|
||||||
|
check_is_installed=if ! which $(1) > /dev/null; then echo "Please install $(1)"; exit 1; fi
|
||||||
|
check-build-essentials:
|
||||||
|
@$(call check_is_installed,zip)
|
||||||
|
@$(call check_is_installed,unzip)
|
||||||
|
@$(call check_is_installed,tar)
|
||||||
|
@$(call check_is_installed,curl)
|
||||||
|
ifneq "${GOOS}" "windows"
|
||||||
|
@$(call check_is_installed,cmake)
|
||||||
|
@$(call check_is_installed,ninja)
|
||||||
|
endif
|
||||||
|
|
||||||
add-license:
|
add-license:
|
||||||
./utils/missing_license.sh add
|
./utils/missing_license.sh add
|
||||||
@ -194,27 +229,50 @@ add-license:
|
|||||||
change-copyright-year:
|
change-copyright-year:
|
||||||
./utils/missing_license.sh change-year
|
./utils/missing_license.sh change-year
|
||||||
|
|
||||||
|
GOCOVERAGE=-covermode=count -coverpkg=github.com/ProtonMail/proton-bridge/v3/internal/...,github.com/ProtonMail/proton-bridge/v3/pkg/...,
|
||||||
|
GOCOVERDIR=-args -test.gocoverdir=$$PWD/coverage
|
||||||
|
|
||||||
test: gofiles
|
test: gofiles
|
||||||
@# Listing packages manually to not run Qt folder (which needs to run qtsetup first) and integration tests.
|
mkdir -p coverage/unit-${GOOS}
|
||||||
go test -coverprofile=/tmp/coverage.out -run=${TESTRUN} \
|
go test \
|
||||||
./internal/api/... \
|
-v -timeout=20m -p=1 -count=1 \
|
||||||
./internal/bridge/... \
|
${GOCOVERAGE} \
|
||||||
./internal/config/... \
|
-run=${TESTRUN} ./internal/... ./pkg/... \
|
||||||
./internal/constants/... \
|
${GOCOVERDIR}/unit-${GOOS}
|
||||||
./internal/cookies/... \
|
|
||||||
./internal/crash/... \
|
test-race: gofiles
|
||||||
./internal/events/... \
|
go test -v -timeout=40m -p=1 -count=1 -race -failfast -run=${TESTRUN} ./internal/... ./pkg/...
|
||||||
./internal/frontend/cli/... \
|
|
||||||
./internal/imap/... \
|
test-integration: gofiles
|
||||||
./internal/locations/... \
|
mkdir -p coverage/integration
|
||||||
./internal/logging/... \
|
go test \
|
||||||
./internal/metrics/... \
|
-v -timeout=60m -p=1 -count=1 -tags=test_integration \
|
||||||
./internal/smtp/... \
|
${GOCOVERAGE} \
|
||||||
./internal/store/... \
|
github.com/ProtonMail/proton-bridge/v3/tests \
|
||||||
./internal/updater/... \
|
${GOCOVERDIR}/integration
|
||||||
./internal/users/... \
|
|
||||||
./internal/versioner/... \
|
|
||||||
./pkg/...
|
test-integration-debug: gofiles
|
||||||
|
dlv test github.com/ProtonMail/proton-bridge/v3/tests -- -test.v -test.timeout=10m -test.parallel=1 -test.count=1
|
||||||
|
|
||||||
|
test-integration-race: gofiles
|
||||||
|
go test -v -timeout=60m -p=1 -count=1 -race -failfast github.com/ProtonMail/proton-bridge/v3/tests
|
||||||
|
|
||||||
|
test-integration-nightly: gofiles
|
||||||
|
mkdir -p coverage/integration
|
||||||
|
go test \
|
||||||
|
-v -timeout=90m -p=1 -count=1 -tags=test_integration \
|
||||||
|
${GOCOVERAGE} \
|
||||||
|
github.com/ProtonMail/proton-bridge/v3/tests \
|
||||||
|
${GOCOVERDIR}/integration \
|
||||||
|
nightly
|
||||||
|
|
||||||
|
fuzz: gofiles
|
||||||
|
go test -fuzz=FuzzUnmarshal -parallel=4 -fuzztime=60s $(PWD)/internal/legacy/credentials
|
||||||
|
go test -fuzz=FuzzNewParser -parallel=4 -fuzztime=60s $(PWD)/pkg/message/parser
|
||||||
|
go test -fuzz=FuzzReadHeaderBody -parallel=4 -fuzztime=60s $(PWD)/pkg/message
|
||||||
|
go test -fuzz=FuzzDecodeHeader -parallel=4 -fuzztime=60s $(PWD)/pkg/mime
|
||||||
|
go test -fuzz=FuzzDecodeCharset -parallel=4 -fuzztime=60s $(PWD)/pkg/mime
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
go test -run '^$$' -bench=. -memprofile bench_mem.pprof -cpuprofile bench_cpu.pprof ./internal/store
|
go test -run '^$$' -bench=. -memprofile bench_mem.pprof -cpuprofile bench_cpu.pprof ./internal/store
|
||||||
@ -224,18 +282,31 @@ bench:
|
|||||||
coverage: test
|
coverage: test
|
||||||
go tool cover -html=/tmp/coverage.out -o=coverage.html
|
go tool cover -html=/tmp/coverage.out -o=coverage.html
|
||||||
|
|
||||||
integration-test-bridge:
|
|
||||||
${MAKE} -C test test-bridge
|
|
||||||
|
|
||||||
mocks:
|
mocks:
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/users Locator,PanicHandler,CredentialsStorer,StoreMaker > internal/users/mocks/mocks.go
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/bridge TLSReporter,ProxyController,Autostarter > tmp
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/listener Listener > internal/users/mocks/listener_mocks.go
|
mv tmp internal/bridge/mocks/mocks.go
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,BridgeUser,ChangeNotifier,Storer > internal/store/mocks/mocks.go
|
mockgen --package mocks github.com/ProtonMail/gluon/async PanicHandler > internal/bridge/mocks/async_mocks.go
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/listener Listener > internal/store/mocks/utils_mocks.go
|
mockgen --package mocks github.com/ProtonMail/gluon/reporter Reporter > internal/bridge/mocks/gluon_mocks.go
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/pmapi Client,Manager > pkg/pmapi/mocks/mocks.go
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/updater Downloader,Installer > internal/updater/mocks/mocks.go
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/message Fetcher > pkg/message/mocks/mocks.go
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/telemetry HeartbeatManager > internal/telemetry/mocks/mocks.go
|
||||||
|
cp internal/telemetry/mocks/mocks.go internal/bridge/mocks/telemetry_mocks.go
|
||||||
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/services/userevents \
|
||||||
|
EventSource,EventIDStore > internal/services/userevents/mocks/mocks.go
|
||||||
|
mockgen --package userevents github.com/ProtonMail/proton-bridge/v3/internal/services/userevents \
|
||||||
|
EventSubscriber,MessageEventHandler,LabelEventHandler,AddressEventHandler,RefreshEventHandler,UserEventHandler,UserUsedSpaceEventHandler > tmp
|
||||||
|
mv tmp internal/services/userevents/mocks_test.go
|
||||||
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/events EventPublisher \
|
||||||
|
> internal/events/mocks/mocks.go
|
||||||
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/services/useridentity IdentityProvider,Telemetry \
|
||||||
|
> internal/services/useridentity/mocks/mocks.go
|
||||||
|
mockgen --self_package "github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice" -package syncservice github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice \
|
||||||
|
ApplyStageInput,BuildStageInput,BuildStageOutput,DownloadStageInput,DownloadStageOutput,MetadataStageInput,MetadataStageOutput,\
|
||||||
|
StateProvider,Regulator,UpdateApplier,MessageBuilder,APIClient,Reporter,DownloadRateModifier \
|
||||||
|
> tmp
|
||||||
|
mv tmp internal/services/syncservice/mocks_test.go
|
||||||
|
mockgen --package mocks github.com/ProtonMail/gluon/connector IMAPStateWrite > internal/services/imapservice/mocks/mocks.go
|
||||||
|
|
||||||
lint: gofiles lint-golang lint-license lint-dependencies lint-changelog
|
lint: gofiles lint-golang lint-license lint-dependencies lint-changelog lint-bug-report
|
||||||
|
|
||||||
lint-license:
|
lint-license:
|
||||||
./utils/missing_license.sh check
|
./utils/missing_license.sh check
|
||||||
@ -251,6 +322,12 @@ lint-golang:
|
|||||||
$(info linting with GOMAXPROCS=${GOMAXPROCS})
|
$(info linting with GOMAXPROCS=${GOMAXPROCS})
|
||||||
golangci-lint run ./...
|
golangci-lint run ./...
|
||||||
|
|
||||||
|
lint-bug-report:
|
||||||
|
python3 utils/validate_bug_report_file.py --file "internal/frontend/bridge-gui/bridge-gui/qml/Resources/bug_report_flow.json"
|
||||||
|
|
||||||
|
lint-bug-report-preview:
|
||||||
|
python3 utils/validate_bug_report_file.py --file "internal/frontend/bridge-gui/bridge-gui/qml/Resources/bug_report_flow.json" --preview
|
||||||
|
|
||||||
updates: install-go-mod-outdated
|
updates: install-go-mod-outdated
|
||||||
# Uncomment the "-ci" to fail the job if something can be updated.
|
# Uncomment the "-ci" to fail the job if something can be updated.
|
||||||
go list -u -m -json all | go-mod-outdated -update -direct #-ci
|
go list -u -m -json all | go-mod-outdated -update -direct #-ci
|
||||||
@ -258,7 +335,7 @@ updates: install-go-mod-outdated
|
|||||||
doc:
|
doc:
|
||||||
godoc -http=:6060
|
godoc -http=:6060
|
||||||
|
|
||||||
release-notes: release-notes/bridge_stable.html release-notes/bridge_early.html
|
release-notes: release-notes/bridge_stable.html release-notes/bridge_early.html utils/release_notes.sh
|
||||||
|
|
||||||
release-notes/%.html: release-notes/%.md
|
release-notes/%.html: release-notes/%.md
|
||||||
./utils/release_notes.sh $^
|
./utils/release_notes.sh $^
|
||||||
@ -271,51 +348,79 @@ gofiles: ./internal/bridge/credits.go
|
|||||||
cd ./utils/ && ./credits.sh bridge
|
cd ./utils/ && ./credits.sh bridge
|
||||||
|
|
||||||
## Run and debug
|
## Run and debug
|
||||||
.PHONY: run run-qt run-qt-cli run-nogui run-nogui-cli run-debug run-qml-preview clean-vendor clean-frontend-qt clean-frontend-qt-common clean
|
.PHONY: run run-qt run-qt-cli run-nogui run-cli run-noninteractive run-debug run-gui-tester clean-vendor clean-frontend-qt clean-frontend-qt-common clean
|
||||||
|
|
||||||
LOG?=debug
|
LOG?=debug
|
||||||
LOG_IMAP?=client # client/server/all, or empty to turn it off
|
LOG_IMAP?=client # client/server/all, or empty to turn it off
|
||||||
LOG_SMTP?=--log-smtp # empty to turn it off
|
LOG_SMTP?=--log-smtp # empty to turn it off
|
||||||
RUN_FLAGS?=-m -l=${LOG} --log-imap=${LOG_IMAP} ${LOG_SMTP}
|
RUN_FLAGS?=-l=${LOG} --log-imap=${LOG_IMAP} ${LOG_SMTP}
|
||||||
|
|
||||||
run: run-nogui-cli
|
run: run-qt
|
||||||
|
|
||||||
run-qt: ${EXE_TARGET}
|
run-cli: run-nogui
|
||||||
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} 2>&1 | tee last.log
|
|
||||||
run-qt-cli: ${EXE_TARGET}
|
|
||||||
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} -c
|
|
||||||
|
|
||||||
run-nogui: clean-vendor gofiles
|
run-noninteractive: build-nogui clean-vendor gofiles
|
||||||
PROTONMAIL_ENV=dev go run ${BUILD_FLAGS} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} | tee last.log
|
PROTONMAIL_ENV=dev ./${LAUNCHER_EXE} ${RUN_FLAGS} -n
|
||||||
run-nogui-cli: clean-vendor gofiles
|
|
||||||
PROTONMAIL_ENV=dev go run ${BUILD_FLAGS} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} -c
|
run-qt: build-gui
|
||||||
|
ifeq "${TARGET_OS}" "darwin"
|
||||||
|
PROTONMAIL_ENV=dev ${DARWINAPP_CONTENTS}/MacOS/${LAUNCHER_EXE} ${RUN_FLAGS}
|
||||||
|
else
|
||||||
|
PROTONMAIL_ENV=dev ./${DEPLOY_DIR}/${TARGET_OS}/${LAUNCHER_EXE} ${RUN_FLAGS}
|
||||||
|
endif
|
||||||
|
|
||||||
|
run-nogui: build-nogui clean-vendor gofiles
|
||||||
|
PROTONMAIL_ENV=dev ./${LAUNCHER_EXE} ${RUN_FLAGS} -c
|
||||||
|
|
||||||
run-debug:
|
run-debug:
|
||||||
PROTONMAIL_ENV=dev dlv debug --build-flags "${BUILD_FLAGS}" cmd/${TARGET_CMD}/main.go -- ${RUN_FLAGS} --noninteractive
|
dlv debug \
|
||||||
|
--build-flags "-ldflags '-X github.com/ProtonMail/proton-bridge/v3/internal/constants.Version=3.1.0+git'" \
|
||||||
|
./cmd/Desktop-Bridge/main.go \
|
||||||
|
-- \
|
||||||
|
-n -l=trace
|
||||||
|
|
||||||
run-qml-preview:
|
ifeq "${TARGET_OS}" "windows"
|
||||||
find internal/frontend/qml/ -iname '*qmlc' | xargs rm -f
|
EXE_SUFFIX=.exe
|
||||||
bridge_preview internal/frontend/qml/Bridge_test.qml
|
endif
|
||||||
|
|
||||||
|
|
||||||
clean-frontend-qt:
|
bridge-gui-tester: build-gui
|
||||||
$(MAKE) -C internal/frontend -f Makefile.local clean
|
cp ./cmd/Desktop-Bridge/deploy/${TARGET_OS}/bridge-gui${EXE_SUFFIX} .
|
||||||
|
cd ./internal/frontend/bridge-gui/bridge-gui-tester && cmake . && make
|
||||||
|
|
||||||
clean-vendor: clean-frontend-qt clean-frontend-qt-common
|
run-gui-tester: bridge-gui-tester
|
||||||
|
# copying tester as bridge so bridge-gui will start it and connect to it automatically
|
||||||
|
cp ./internal/frontend/bridge-gui/bridge-gui-tester/bridge-gui-tester${EXE_SUFFIX} bridge${EXE_SUFFIX}
|
||||||
|
./bridge-gui${EXE_SUFFIX}
|
||||||
|
|
||||||
|
|
||||||
|
clean-vendor:
|
||||||
rm -rf ./vendor
|
rm -rf ./vendor
|
||||||
|
|
||||||
clean: clean-vendor
|
clean-gui:
|
||||||
|
cd internal/frontend/bridge-gui/ && \
|
||||||
|
rm -f BuildConfig.h && \
|
||||||
|
rm -rf cmake-build-*/
|
||||||
|
|
||||||
|
clean-vcpkg:
|
||||||
|
git submodule deinit -f ./extern/vcpkg
|
||||||
|
rm -rf ./.git/submodule/vcpkg
|
||||||
|
rm -rf ./extern/vcpkg
|
||||||
|
git checkout -- extern/vcpkg
|
||||||
|
|
||||||
|
clean: clean-vendor clean-gui clean-vcpkg
|
||||||
rm -rf vendor-cache
|
rm -rf vendor-cache
|
||||||
rm -rf cmd/Desktop-Bridge/deploy
|
rm -rf cmd/Desktop-Bridge/deploy
|
||||||
rm -rf cmd/Import-Export/deploy
|
rm -rf cmd/Import-Export/deploy
|
||||||
rm -f build last.log mem.pprof main.go
|
rm -f build last.log mem.pprof main.go
|
||||||
rm -f resource.syso
|
rm -f ./*.syso
|
||||||
rm -f release-notes/bridge.html
|
rm -f release-notes/bridge.html
|
||||||
rm -f release-notes/import-export.html
|
rm -f release-notes/import-export.html
|
||||||
|
rm -f ${LAUNCHER_EXE} ${BRIDGE_EXE} ${BRIDGE_EXE_NAME}
|
||||||
|
|
||||||
|
|
||||||
.PHONY: generate
|
.PHONY: generate
|
||||||
generate:
|
generate:
|
||||||
go generate ./...
|
go generate ./...
|
||||||
$(MAKE) add-license
|
$(MAKE) build
|
||||||
|
|
||||||
.FORCE:
|
.FORCE:
|
||||||
|
|||||||
75
README.md
@ -1,5 +1,5 @@
|
|||||||
# Proton Mail Bridge and Import Export app
|
# Proton Mail Bridge and Import Export app
|
||||||
Copyright (c) 2022 Proton AG
|
Copyright (c) 2023 Proton AG
|
||||||
|
|
||||||
This repository holds the Proton Mail Bridge and the Proton Mail Import-Export applications.
|
This repository holds the Proton Mail Bridge and the Proton Mail Import-Export applications.
|
||||||
For a detailed build information see [BUILDS](./BUILDS.md).
|
For a detailed build information see [BUILDS](./BUILDS.md).
|
||||||
@ -22,22 +22,7 @@ to start Bridge on startup is enabled by default.
|
|||||||
When the main window is closed, Bridge will continue to run in the
|
When the main window is closed, Bridge will continue to run in the
|
||||||
background.
|
background.
|
||||||
|
|
||||||
More details [on the public website](https://protonmail.com/bridge).
|
More details [on the public website](https://proton.me/mail/bridge).
|
||||||
|
|
||||||
## Description Import-Export app
|
|
||||||
Proton Mail Import-Export app for importing and exporting messages.
|
|
||||||
|
|
||||||
To transfer messages, firstly log in using your Proton Mail credentials.
|
|
||||||
For import, expand your account, and pick the address to which to import
|
|
||||||
messages from IMAP server or local EML or MBOX files. For export, pick
|
|
||||||
the whole account or only a specific address. Then, in both cases,
|
|
||||||
configure transfer rules (match source and target mailboxes, set time
|
|
||||||
range limits and so on) and hit start. Once the transfer is complete,
|
|
||||||
check the results.
|
|
||||||
|
|
||||||
More details [on the public website](https://protonmail.com/import-export).
|
|
||||||
|
|
||||||
The Import-Export app is developed in separate branch `master-ie`.
|
|
||||||
|
|
||||||
## Launchers
|
## Launchers
|
||||||
Launchers are binaries used to run the Proton Mail Bridge or Import-Export apps.
|
Launchers are binaries used to run the Proton Mail Bridge or Import-Export apps.
|
||||||
@ -63,9 +48,6 @@ major problems.
|
|||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
### Bridge application
|
|
||||||
- `BRIDGESTRICTMODE`: tells bridge to turn on `bbolt`'s "strict mode" which checks the database after every `Commit`. Set to `1` to enable.
|
|
||||||
|
|
||||||
### Dev build or run
|
### Dev build or run
|
||||||
- `APP_VERSION`: set the bridge app version used during testing or building
|
- `APP_VERSION`: set the bridge app version used during testing or building
|
||||||
- `PROTONMAIL_ENV`: when set to `dev` it is not using Sentry to report crashes
|
- `PROTONMAIL_ENV`: when set to `dev` it is not using Sentry to report crashes
|
||||||
@ -77,35 +59,34 @@ major problems.
|
|||||||
- `TAGS`: set build tags for tests
|
- `TAGS`: set build tags for tests
|
||||||
- `FEATURES`: set feature dir, file or scenario to test
|
- `FEATURES`: set feature dir, file or scenario to test
|
||||||
|
|
||||||
|
## Folders
|
||||||
|
|
||||||
|
There are now three types of system folders which Bridge recognises:
|
||||||
|
|
||||||
|
| | Windows | Mac | Linux | Linux (XDG) |
|
||||||
|
|--------|-------------------------------------|-----------------------------------------------------|-------------------------------------|---------------------------------------|
|
||||||
|
| config | %APPDATA%\protonmail\bridge-v3 | ~/Library/Application Support/protonmail/bridge-v3 | ~/.config/protonmail/bridge-v3 | $XDG_CONFIG_HOME/protonmail/bridge-v3 |
|
||||||
|
| cache | %LOCALAPPDATA%\protonmail\bridge-v3 | ~/Library/Caches/protonmail/bridge-v3 | ~/.cache/protonmail/bridge-v3 | $XDG_CACHE_HOME/protonmail/bridge-v3 |
|
||||||
|
| data | %APPDATA%\protonmail\bridge-v3 | ~/Library/Application Support/protonmail/bridge-v3 | ~/.local/share/protonmail/bridge-v3 | $XDG_DATA_HOME/protonmail/bridge-v3 |
|
||||||
|
| temp | %LOCALAPPDATA%\Temp | $TMPDIR if non-empty, else /tmp | $TMPDIR if non-empty, else /tmp | $TMPDIR if non-empty, else /tmp |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
### Database
|
|
||||||
The database stores metadata necessary for presenting messages and mailboxes to an email client:
|
|
||||||
- Linux: `~/.cache/protonmail/bridge/<cacheVersion>/mailbox-<userID>.db` (unless `XDG_CACHE_HOME` is set, in which case that is used as your `~`)
|
|
||||||
- macOS: `~/Library/Caches/protonmail/bridge/<cacheVersion>/mailbox-<userID>.db`
|
|
||||||
- Windows: `%LOCALAPPDATA%\protonmail\bridge\<cacheVersion>\mailbox-<userID>.db`
|
|
||||||
|
|
||||||
### Preferences
|
| | Base Dir | Path |
|
||||||
User preferences are stored in json at the following location:
|
|------------------------|----------|----------------------------|
|
||||||
- Linux: `~/.config/protonmail/bridge/prefs.json`
|
| bridge lock file | cache | bridge.lock |
|
||||||
- macOS: `~/Library/ApplicationSupport/protonmail/bridge/prefs.json`
|
| bridge-gui lock file | cache | bridge-gui.lock |
|
||||||
- Windows: `%APPDATA%\protonmail\bridge\prefs.json`
|
| vault | config | vault.enc |
|
||||||
|
| gRPC server json | config | grpcServerConfig.json |
|
||||||
|
| gRPC client json | config | grpcClientConfig_<id>.json |
|
||||||
|
| gRPC Focus server json | config | grpcFocusServerConfig.json |
|
||||||
|
| Logs | data | logs |
|
||||||
|
| gluon DB | data | gluon/backend/db |
|
||||||
|
| gluon messages | data | gluon/backend/store |
|
||||||
|
| Update files | data | updates |
|
||||||
|
| sentry cache | data | sentry_cache |
|
||||||
|
| Mac/Linux File Socket | temp | bridge{4_DIGITS} |
|
||||||
|
|
||||||
### IMAP Cache
|
|
||||||
The currently subscribed mailboxes are held in a json file:
|
|
||||||
- Linux: `~/.cache/protonmail/bridge/<cacheVersion>/user_info.json` (unless `XDG_CACHE_HOME` is set, in which case that is used as your `~`)
|
|
||||||
- macOS: `~/Library/Caches/protonmail/bridge/<cacheVersion>/user_info.json`
|
|
||||||
- Windows: `%LOCALAPPDATA%\protonmail\bridge\<cacheVersion>\user_info.json`
|
|
||||||
|
|
||||||
### Lock file
|
|
||||||
Bridge utilises an on-disk lock to ensure only one instance is run at once. The lock file is here:
|
|
||||||
- Linux: `~/.cache/protonmail/bridge/<cacheVersion>/bridge.lock` (unless `XDG_CACHE_HOME` is set, in which case that is used as your `~`)
|
|
||||||
- macOS: `~/Library/Caches/protonmail/bridge/<cacheVersion>/bridge.lock`
|
|
||||||
- Windows: `%LOCALAPPDATA%\protonmail\bridge\<cacheVersion>\bridge.lock`
|
|
||||||
|
|
||||||
### TLS Certificate and Key
|
|
||||||
When bridge first starts, it generates a unique TLS certificate and key file at the following locations:
|
|
||||||
- Linux: `~/.config/protonmail/bridge/{cert,key}.pem` (unless `XDG_CONFIG_HOME` is set, in which case that is used as your `~/.config`)
|
|
||||||
- macOS: `~/Library/ApplicationSupport/protonmail/bridge/{cert,key}.pem`
|
|
||||||
- Windows: `%APPDATA%\protonmail\bridge\{cert,key}.pem`
|
|
||||||
|
|
||||||
|
|||||||
69
ci/build.yml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
.script-build:
|
||||||
|
stage: build
|
||||||
|
needs: ["lint"]
|
||||||
|
extends:
|
||||||
|
- .rules-branch-and-MR-manual
|
||||||
|
script:
|
||||||
|
- make build
|
||||||
|
- git diff && git diff-index --quiet HEAD
|
||||||
|
- make vault-editor
|
||||||
|
artifacts:
|
||||||
|
expire_in: 1 day
|
||||||
|
when: always
|
||||||
|
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||||
|
paths:
|
||||||
|
- bridge_*.tgz
|
||||||
|
- vault-editor
|
||||||
|
|
||||||
|
build-linux:
|
||||||
|
extends:
|
||||||
|
- .script-build
|
||||||
|
- .env-linux-build
|
||||||
|
|
||||||
|
build-linux-qa:
|
||||||
|
extends:
|
||||||
|
- build-linux
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
variables:
|
||||||
|
BUILD_TAGS: "build_qa"
|
||||||
|
|
||||||
|
build-darwin:
|
||||||
|
extends:
|
||||||
|
- .script-build
|
||||||
|
- .env-darwin
|
||||||
|
|
||||||
|
build-darwin-qa:
|
||||||
|
extends:
|
||||||
|
- build-darwin
|
||||||
|
variables:
|
||||||
|
BUILD_TAGS: "build_qa"
|
||||||
|
|
||||||
|
build-windows:
|
||||||
|
extends:
|
||||||
|
- .script-build
|
||||||
|
- .env-windows
|
||||||
|
|
||||||
|
build-windows-qa:
|
||||||
|
extends:
|
||||||
|
- build-windows
|
||||||
|
variables:
|
||||||
|
BUILD_TAGS: "build_qa"
|
||||||
|
|
||||||
|
trigger-qa-installer:
|
||||||
|
stage: build
|
||||||
|
needs: ["lint"]
|
||||||
|
extends:
|
||||||
|
- .rules-br-tag-always-branch-and-MR-manual
|
||||||
|
variables:
|
||||||
|
APP: bridge
|
||||||
|
WORKFLOW: build-all
|
||||||
|
SRC_TAG: $CI_COMMIT_BRANCH
|
||||||
|
TAG: $CI_COMMIT_TAG
|
||||||
|
SRC_HASH: $CI_COMMIT_SHA
|
||||||
|
trigger:
|
||||||
|
project: "jcuth/bridge-release"
|
||||||
|
branch: master
|
||||||
|
|
||||||
62
ci/env.yml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
.env-windows:
|
||||||
|
before_script:
|
||||||
|
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
|
||||||
|
- export GOROOT=/c/Go1.20/
|
||||||
|
- export PATH=$GOROOT/bin:$PATH
|
||||||
|
- export GOARCH=amd64
|
||||||
|
- export GOPATH=~/go1.20
|
||||||
|
- export GO111MODULE=on
|
||||||
|
- export PATH="${GOPATH}/bin:${PATH}"
|
||||||
|
- export MSYSTEM=
|
||||||
|
- export QT6DIR=/c/grrrQt/6.4.3/msvc2019_64
|
||||||
|
- export PATH=$PATH:${QT6DIR}/bin
|
||||||
|
- export PATH="/c/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin:$PATH"
|
||||||
|
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
|
||||||
|
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
||||||
|
- git config --global safe.directory '*'
|
||||||
|
- git status --porcelain
|
||||||
|
cache: {}
|
||||||
|
tags:
|
||||||
|
- windows-bridge
|
||||||
|
|
||||||
|
.env-darwin:
|
||||||
|
before_script:
|
||||||
|
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
|
||||||
|
- export PATH=/usr/local/bin:$PATH
|
||||||
|
- export PATH=/usr/local/opt/git/bin:$PATH
|
||||||
|
- export PATH=/usr/local/opt/make/libexec/gnubin:$PATH
|
||||||
|
- export PATH=/usr/local/opt/gnu-sed/libexec/gnubin:$PATH
|
||||||
|
- export GOROOT=~/local/opt/go@1.20
|
||||||
|
- export PATH="${GOROOT}/bin:$PATH"
|
||||||
|
- export GOPATH=~/go1.20
|
||||||
|
- export PATH="${GOPATH}/bin:$PATH"
|
||||||
|
- export QT6DIR=/opt/Qt/6.4.3/macos
|
||||||
|
- export PATH="${QT6DIR}/bin:$PATH"
|
||||||
|
- uname -a
|
||||||
|
cache: {}
|
||||||
|
tags:
|
||||||
|
- macos-m1-bridge
|
||||||
|
|
||||||
|
.env-linux-build:
|
||||||
|
image: gitlab.protontech.ch:4567/go/bridge-internal:build-go1.20-qt6.4.3
|
||||||
|
variables:
|
||||||
|
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
||||||
|
cache:
|
||||||
|
key: linux-vcpkg
|
||||||
|
paths:
|
||||||
|
- .cache
|
||||||
|
when: 'always'
|
||||||
|
before_script:
|
||||||
|
- mkdir -p .cache/bin
|
||||||
|
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
|
||||||
|
- export PATH=$(pwd)/.cache/bin:$PATH
|
||||||
|
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
||||||
|
- export PATH=$PATH:$QT6DIR/bin
|
||||||
|
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
|
||||||
|
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
||||||
|
tags:
|
||||||
|
- shared-large
|
||||||
|
|
||||||
58
ci/rules.yml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
.rules-branch-and-MR-manual:
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- when: never
|
||||||
|
|
||||||
|
.rules-branch-manual-MR-and-devel-always:
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == "devel" || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
when: always
|
||||||
|
allow_failure: false
|
||||||
|
- if: $CI_COMMIT_BRANCH
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- when: never
|
||||||
|
|
||||||
|
.rules-branch-manual-br-tag-and-MR-and-devel-always:
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == "devel" || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
when: always
|
||||||
|
allow_failure: false
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_TAG =~ /^br-\d+/
|
||||||
|
when: always
|
||||||
|
allow_failure: false
|
||||||
|
- if: $CI_COMMIT_BRANCH
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- when: never
|
||||||
|
|
||||||
|
.rules-branch-manual-scheduled-and-test-branch-always:
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||||
|
when: always
|
||||||
|
allow_failure: false
|
||||||
|
- if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME=~ /^test/
|
||||||
|
when: always
|
||||||
|
allow_failure: false
|
||||||
|
- if: $CI_COMMIT_BRANCH
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- when: never
|
||||||
|
|
||||||
|
.rules-br-tag-always-branch-and-MR-manual:
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == 'push' && $CI_COMMIT_BRANCH
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_TAG =~ /^br-\d+/
|
||||||
|
when: always
|
||||||
|
- when: never
|
||||||
|
|
||||||
128
ci/test.yml
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
lint:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .rules-branch-manual-br-tag-and-MR-and-devel-always
|
||||||
|
script:
|
||||||
|
- make lint
|
||||||
|
tags:
|
||||||
|
- shared-medium
|
||||||
|
|
||||||
|
bug-report-preview:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .rules-branch-and-MR-manual
|
||||||
|
script:
|
||||||
|
- make lint-bug-report-preview
|
||||||
|
tags:
|
||||||
|
- shared-medium
|
||||||
|
|
||||||
|
.script-test:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
script:
|
||||||
|
- make test
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- coverage/**
|
||||||
|
|
||||||
|
test-linux:
|
||||||
|
extends:
|
||||||
|
- .script-test
|
||||||
|
tags:
|
||||||
|
- shared-large
|
||||||
|
|
||||||
|
fuzz-linux:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
script:
|
||||||
|
- make fuzz
|
||||||
|
tags:
|
||||||
|
- shared-large
|
||||||
|
|
||||||
|
test-linux-race:
|
||||||
|
extends:
|
||||||
|
- test-linux
|
||||||
|
- .rules-branch-and-MR-manual
|
||||||
|
script:
|
||||||
|
- make test-race
|
||||||
|
|
||||||
|
test-integration:
|
||||||
|
extends:
|
||||||
|
- test-linux
|
||||||
|
script:
|
||||||
|
- make test-integration
|
||||||
|
|
||||||
|
test-integration-race:
|
||||||
|
extends:
|
||||||
|
- test-integration
|
||||||
|
- .rules-branch-and-MR-manual
|
||||||
|
script:
|
||||||
|
- make test-integration-race
|
||||||
|
|
||||||
|
test-integration-nightly:
|
||||||
|
extends:
|
||||||
|
- test-integration
|
||||||
|
- .rules-branch-manual-scheduled-and-test-branch-always
|
||||||
|
needs:
|
||||||
|
- test-integration
|
||||||
|
script:
|
||||||
|
- make test-integration-nightly | tee -a nightly-job.log
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
paths:
|
||||||
|
- nightly-job.log
|
||||||
|
|
||||||
|
test-windows:
|
||||||
|
extends:
|
||||||
|
- .env-windows
|
||||||
|
- .script-test
|
||||||
|
|
||||||
|
test-darwin:
|
||||||
|
extends:
|
||||||
|
- .env-darwin
|
||||||
|
- .script-test
|
||||||
|
|
||||||
|
test-coverage:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .rules-branch-manual-scheduled-and-test-branch-always
|
||||||
|
script:
|
||||||
|
- ./utils/coverage.sh
|
||||||
|
coverage: '/total:.*\(statements\).*\d+\.\d+%/'
|
||||||
|
needs:
|
||||||
|
- test-linux
|
||||||
|
- test-windows
|
||||||
|
- test-darwin
|
||||||
|
- test-integration
|
||||||
|
- test-integration-nightly
|
||||||
|
tags:
|
||||||
|
- shared-small
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- coverage*
|
||||||
|
- coverage/**
|
||||||
|
when: 'always'
|
||||||
|
reports:
|
||||||
|
coverage_report:
|
||||||
|
coverage_format: cobertura
|
||||||
|
path: coverage.xml
|
||||||
|
|
||||||
|
go-vuln-check:
|
||||||
|
extends:
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
stage: test
|
||||||
|
tags:
|
||||||
|
- shared-medium
|
||||||
|
script:
|
||||||
|
- apt-get -y install jq
|
||||||
|
- ./utils/govulncheck.sh
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
paths:
|
||||||
|
- vulns*
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
// Copyright (c) 2023 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -17,6 +17,14 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/app"
|
||||||
|
"github.com/bradenaw/juniper/xslices"
|
||||||
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
___....___
|
___....___
|
||||||
^^ __..-:'':__:..:__:'':-..__
|
^^ __..-:'':__:..:__:'':-..__
|
||||||
@ -34,41 +42,6 @@ package main
|
|||||||
~~^_~^~/ \~^-~^~ _~^-~_^~-^~_^~~-^~_~^~-~_~-^~_^/ \~^ ~~_ ^
|
~~^_~^~/ \~^-~^~ _~^-~_^~-^~_^~~-^~_~^~-~_~-^~_^/ \~^ ~~_ ^
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/app/base"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/app/bridge"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
appName = "Proton Mail Bridge"
|
|
||||||
appUsage = "Proton Mail IMAP and SMTP Bridge"
|
|
||||||
configName = "bridge"
|
|
||||||
updateURLName = "bridge"
|
|
||||||
keychainName = "bridge"
|
|
||||||
cacheVersion = "c11"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
base, err := base.New(
|
_ = app.New().Run(xslices.Filter(os.Args, func(arg string) bool { return !strings.Contains(arg, "-psn_") }))
|
||||||
appName,
|
|
||||||
appUsage,
|
|
||||||
configName,
|
|
||||||
updateURLName,
|
|
||||||
keychainName,
|
|
||||||
cacheVersion,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Fatal("Failed to create app base")
|
|
||||||
}
|
|
||||||
// Other instance already running.
|
|
||||||
if base == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := bridge.New(base).Run(os.Args); err != nil {
|
|
||||||
logrus.WithError(err).Fatal("Bridge exited with error")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
// Copyright (c) 2023 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -18,137 +18,224 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/gluon/async"
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/crash"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/crash"
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/locations"
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/logging"
|
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/sentry"
|
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/versioner"
|
"github.com/ProtonMail/proton-bridge/v3/internal/versioner"
|
||||||
|
"github.com/bradenaw/juniper/xslices"
|
||||||
|
"github.com/elastic/go-sysinfo"
|
||||||
|
"github.com/elastic/go-sysinfo/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/execabs"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
appName = "Proton Mail Launcher"
|
appName = "Proton Mail Launcher"
|
||||||
configName = "bridge"
|
exeName = "bridge"
|
||||||
exeName = "proton-bridge"
|
guiName = "bridge-gui"
|
||||||
|
launcherName = "launcher"
|
||||||
|
|
||||||
|
FlagCLI = "cli"
|
||||||
|
FlagCLIShort = "c"
|
||||||
|
FlagNonInteractive = "noninteractive"
|
||||||
|
FlagNonInteractiveShort = "n"
|
||||||
|
FlagLauncher = "--launcher"
|
||||||
|
FlagWait = "--wait"
|
||||||
|
FlagSessionID = "--session-id"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() { //nolint:funlen
|
func main() { //nolint:funlen
|
||||||
reporter := sentry.NewReporter(appName, constants.Version, useragent.New())
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
l := logrus.WithField("launcher_version", constants.Version)
|
||||||
|
|
||||||
|
reporter := sentry.NewReporter(appName, useragent.New())
|
||||||
|
|
||||||
crashHandler := crash.NewHandler(reporter.ReportException)
|
crashHandler := crash.NewHandler(reporter.ReportException)
|
||||||
defer crashHandler.HandlePanic()
|
defer async.HandlePanic(crashHandler)
|
||||||
|
|
||||||
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
|
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, constants.ConfigName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Fatal("Failed to get locations provider")
|
l.WithError(err).Fatal("Failed to get locations provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
locations := locations.New(locationsProvider, configName)
|
locations := locations.New(locationsProvider, constants.ConfigName)
|
||||||
|
|
||||||
logsPath, err := locations.ProvideLogsPath()
|
logsPath, err := locations.ProvideLogsPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Fatal("Failed to get logs path")
|
l.WithError(err).Fatal("Failed to get logs path")
|
||||||
}
|
|
||||||
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
|
|
||||||
|
|
||||||
if err := logging.Init(logsPath); err != nil {
|
|
||||||
logrus.WithError(err).Fatal("Failed to setup logging")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logging.SetLevel(os.Getenv("VERBOSITY"))
|
sessionID := logging.NewSessionID()
|
||||||
|
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath, sessionID, launcherName))
|
||||||
|
|
||||||
|
var closer io.Closer
|
||||||
|
if closer, err = logging.Init(
|
||||||
|
logsPath,
|
||||||
|
sessionID,
|
||||||
|
logging.LauncherShortAppName,
|
||||||
|
logging.DefaultMaxLogFileSize,
|
||||||
|
logging.NoPruning,
|
||||||
|
os.Getenv("VERBOSITY"),
|
||||||
|
); err != nil {
|
||||||
|
l.WithError(err).Fatal("Failed to setup logging")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = logging.Close(closer)
|
||||||
|
}()
|
||||||
|
|
||||||
updatesPath, err := locations.ProvideUpdatesPath()
|
updatesPath, err := locations.ProvideUpdatesPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Fatal("Failed to get updates path")
|
l.WithError(err).Fatal("Failed to get updates path")
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey)
|
key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Fatal("Failed to create new verification key")
|
l.WithError(err).Fatal("Failed to create new verification key")
|
||||||
}
|
}
|
||||||
|
|
||||||
kr, err := crypto.NewKeyRing(key)
|
kr, err := crypto.NewKeyRing(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Fatal("Failed to create new verification keyring")
|
l.WithError(err).Fatal("Failed to create new verification keyring")
|
||||||
}
|
}
|
||||||
|
|
||||||
versioner := versioner.New(updatesPath)
|
versioner := versioner.New(updatesPath)
|
||||||
|
|
||||||
exe, err := getPathToUpdatedExecutable(exeName, versioner, kr, reporter)
|
|
||||||
if err != nil {
|
|
||||||
if exe, err = getFallbackExecutable(exeName, versioner); err != nil {
|
|
||||||
logrus.WithError(err).Fatal("Failed to find any launchable executable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
launcher, err := os.Executable()
|
launcher, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Fatal("Failed to determine path to launcher")
|
logrus.WithError(err).Fatal("Failed to determine path to launcher")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(exe, appendLauncherPath(launcher, os.Args[1:])...) //nolint:gosec
|
l = l.WithField("launcher_path", launcher)
|
||||||
|
|
||||||
|
args := os.Args[1:]
|
||||||
|
|
||||||
|
exe, err := getPathToUpdatedExecutable(filepath.Base(launcher), versioner, kr)
|
||||||
|
if err != nil {
|
||||||
|
exeToLaunch := guiName
|
||||||
|
if inCLIMode(args) {
|
||||||
|
exeToLaunch = exeName
|
||||||
|
}
|
||||||
|
|
||||||
|
l = l.WithField("exe_to_launch", exeToLaunch)
|
||||||
|
l.WithError(err).Info("No more updates found, looking up bridge executable")
|
||||||
|
|
||||||
|
path, err := versioner.GetExecutableInDirectory(exeToLaunch, filepath.Dir(launcher))
|
||||||
|
if err != nil {
|
||||||
|
l.WithError(err).Fatal("No executable in launcher directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
exe = path
|
||||||
|
}
|
||||||
|
|
||||||
|
l = l.WithField("exe_path", exe)
|
||||||
|
|
||||||
|
args, wait, mainExes := findAndStripWait(args)
|
||||||
|
if wait {
|
||||||
|
for _, mainExe := range mainExes {
|
||||||
|
waitForProcessToFinish(mainExe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := execabs.Command(exe, appendLauncherPath(launcher, append(args, FlagSessionID, string(sessionID)))...) //nolint:gosec
|
||||||
|
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
|
||||||
// On windows, if you use Run(), a terminal stays open; we don't want that.
|
// On windows, if you use Run(), a terminal stays open; we don't want that.
|
||||||
if runtime.GOOS == "windows" {
|
if //goland:noinspection GoBoolExpressions
|
||||||
|
runtime.GOOS == "windows" {
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
} else {
|
} else {
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Fatal("Failed to launch")
|
l.WithError(err).Fatal("Failed to launch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appendLauncherPath add launcher path if missing.
|
||||||
func appendLauncherPath(path string, args []string) []string {
|
func appendLauncherPath(path string, args []string) []string {
|
||||||
|
if !sliceContains(args, FlagLauncher) {
|
||||||
|
res := append([]string{}, args...)
|
||||||
|
res = append(res, FlagLauncher, path)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// sliceContains checks if a value is present in a list.
|
||||||
|
func sliceContains[T comparable](list []T, s T) bool {
|
||||||
|
return xslices.Any(list, func(arg T) bool { return arg == s })
|
||||||
|
}
|
||||||
|
|
||||||
|
// inCLIMode detect if CLI mode is asked.
|
||||||
|
func inCLIMode(args []string) bool {
|
||||||
|
return hasFlag(args, FlagCLI) || hasFlag(args, FlagCLIShort) || hasFlag(args, FlagNonInteractive) || hasFlag(args, FlagNonInteractiveShort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasFlag checks if a flag is present in a list.
|
||||||
|
func hasFlag(args []string, flag string) bool {
|
||||||
|
return xslices.Any(args, func(arg string) bool { return (arg == "-"+flag) || (arg == "--"+flag) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// findAndStrip check if a value is present in s list and remove all occurrences of the value from this list.
|
||||||
|
func findAndStrip[T comparable](slice []T, v T) (strippedList []T, found bool) {
|
||||||
|
strippedList = xslices.Filter(slice, func(value T) bool {
|
||||||
|
return value != v
|
||||||
|
})
|
||||||
|
return strippedList, len(strippedList) != len(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findAndStripWait Check for waiter flag get its value and clean them both.
|
||||||
|
func findAndStripWait(args []string) ([]string, bool, []string) {
|
||||||
res := append([]string{}, args...)
|
res := append([]string{}, args...)
|
||||||
|
|
||||||
hasFlag := false
|
hasFlag := false
|
||||||
|
values := make([]string, 0)
|
||||||
for k, v := range res {
|
for k, v := range res {
|
||||||
if v != "--launcher" {
|
if v != FlagWait {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
hasFlag = true
|
|
||||||
|
|
||||||
if k+1 >= len(res) {
|
if k+1 >= len(res) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
hasFlag = true
|
||||||
res[k+1] = path
|
values = append(values, res[k+1])
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasFlag {
|
if hasFlag {
|
||||||
res = append(res, "--launcher", path)
|
res, _ = findAndStrip(res, FlagWait)
|
||||||
|
for _, v := range values {
|
||||||
|
res, _ = findAndStrip(res, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return res, hasFlag, values
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPathToUpdatedExecutable(
|
func getPathToUpdatedExecutable(
|
||||||
name string,
|
name string,
|
||||||
versioner *versioner.Versioner,
|
ver *versioner.Versioner,
|
||||||
kr *crypto.KeyRing,
|
kr *crypto.KeyRing,
|
||||||
reporter *sentry.Reporter,
|
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
versions, err := versioner.ListVersions()
|
versions, err := ver.ListVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to list available versions")
|
return "", errors.Wrap(err, "failed to list available versions")
|
||||||
}
|
}
|
||||||
@ -159,15 +246,15 @@ func getPathToUpdatedExecutable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, version := range versions {
|
for _, version := range versions {
|
||||||
vlog := logrus.WithField("version", version)
|
vlog := logrus.WithFields(logrus.Fields{
|
||||||
|
"version": constants.Version,
|
||||||
|
"check_version": version,
|
||||||
|
"name": name,
|
||||||
|
})
|
||||||
|
|
||||||
if err := version.VerifyFiles(kr); err != nil {
|
if err := version.VerifyFiles(kr); err != nil {
|
||||||
vlog.WithError(err).Error("Files failed verification and will be removed")
|
vlog.WithError(err).Error("Files failed verification and will be removed")
|
||||||
|
|
||||||
if err := reporter.ReportMessage(fmt.Sprintf("version %v failed verification: %v", version, err)); err != nil {
|
|
||||||
vlog.WithError(err).Error("Failed to report corrupt update files")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := version.Remove(); err != nil {
|
if err := version.Remove(); err != nil {
|
||||||
vlog.WithError(err).Error("Failed to remove files")
|
vlog.WithError(err).Error("Failed to remove files")
|
||||||
}
|
}
|
||||||
@ -192,13 +279,45 @@ func getPathToUpdatedExecutable(
|
|||||||
return "", errors.New("no available newer versions")
|
return "", errors.New("no available newer versions")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFallbackExecutable(name string, versioner *versioner.Versioner) (string, error) {
|
// waitForProcessToFinish waits until the process with the given path is finished.
|
||||||
logrus.Info("Searching for fallback executable")
|
func waitForProcessToFinish(exePath string) {
|
||||||
|
for {
|
||||||
|
processes, err := sysinfo.Processes()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Could not determine running processes")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
launcher, err := os.Executable()
|
exeInfo, err := os.Stat(exePath)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).WithField("file", exeInfo).Error("Could not retrieve file info")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if xslices.Any(processes, func(process types.Process) bool {
|
||||||
|
info, err := process.Info()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Trace("Could not retrieve process info")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return sameFile(exeInfo, info.Exe)
|
||||||
|
}) {
|
||||||
|
logrus.Infof("Waiting for %v to finish.", exeInfo.Name())
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameFile(info os.FileInfo, path string) bool {
|
||||||
|
pathInfo, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to determine path to launcher")
|
logrus.WithError(err).WithField("file", path).Error("Could not retrieve file info")
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return versioner.GetExecutableInDirectory(name, filepath.Dir(launcher))
|
return os.SameFile(pathInfo, info)
|
||||||
}
|
}
|
||||||
|
|||||||
80
cmd/launcher/main_test.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/bradenaw/juniper/xslices"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSliceContains(t *testing.T) {
|
||||||
|
assert.True(t, sliceContains([]string{"a", "b", "c"}, "a"))
|
||||||
|
assert.True(t, sliceContains([]int{1, 2, 3}, 2))
|
||||||
|
assert.False(t, sliceContains([]string{"a", "b", "c"}, "A"))
|
||||||
|
assert.False(t, sliceContains([]int{1, 2, 3}, 4))
|
||||||
|
assert.False(t, sliceContains([]string{}, "a"))
|
||||||
|
assert.True(t, sliceContains([]string{"a", "a"}, "a"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindAndStrip(t *testing.T) {
|
||||||
|
list := []string{"a", "b", "c", "c", "b", "c"}
|
||||||
|
|
||||||
|
result, found := findAndStrip(list, "a")
|
||||||
|
assert.True(t, found)
|
||||||
|
assert.True(t, xslices.Equal(result, []string{"b", "c", "c", "b", "c"}))
|
||||||
|
|
||||||
|
result, found = findAndStrip(list, "c")
|
||||||
|
assert.True(t, found)
|
||||||
|
assert.True(t, xslices.Equal(result, []string{"a", "b", "b"}))
|
||||||
|
|
||||||
|
result, found = findAndStrip([]string{"c", "c", "c"}, "c")
|
||||||
|
assert.True(t, found)
|
||||||
|
assert.True(t, xslices.Equal(result, []string{}))
|
||||||
|
|
||||||
|
result, found = findAndStrip(list, "A")
|
||||||
|
assert.False(t, found)
|
||||||
|
assert.True(t, xslices.Equal(result, list))
|
||||||
|
|
||||||
|
result, found = findAndStrip([]string{}, "a")
|
||||||
|
assert.False(t, found)
|
||||||
|
assert.True(t, xslices.Equal(result, []string{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindAndStripWait(t *testing.T) {
|
||||||
|
result, found, values := findAndStripWait([]string{"a", "b", "c"})
|
||||||
|
assert.False(t, found)
|
||||||
|
assert.True(t, xslices.Equal(result, []string{"a", "b", "c"}))
|
||||||
|
assert.True(t, xslices.Equal(values, []string{}))
|
||||||
|
|
||||||
|
result, found, values = findAndStripWait([]string{"a", "--wait", "b"})
|
||||||
|
assert.True(t, found)
|
||||||
|
assert.True(t, xslices.Equal(result, []string{"a"}))
|
||||||
|
assert.True(t, xslices.Equal(values, []string{"b"}))
|
||||||
|
|
||||||
|
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c"})
|
||||||
|
assert.True(t, found)
|
||||||
|
assert.True(t, xslices.Equal(result, []string{"a"}))
|
||||||
|
assert.True(t, xslices.Equal(values, []string{"b", "c"}))
|
||||||
|
|
||||||
|
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c", "--wait", "d"})
|
||||||
|
assert.True(t, found)
|
||||||
|
assert.True(t, xslices.Equal(result, []string{"a"}))
|
||||||
|
assert.True(t, xslices.Equal(values, []string{"b", "c", "d"}))
|
||||||
|
}
|
||||||
BIN
dist/Bridge.icns
vendored
Normal file
BIN
dist/bridge.ico
vendored
Normal file
|
After Width: | Height: | Size: 124 KiB |
32
dist/bridge.svg
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_9588_57903)">
|
||||||
|
<path d="M127.416 -0.0898438C63.1423 -0.0898438 11.0449 51.9864 11.0449 116.234V233.331C11.0449 245.779 21.1405 255.871 33.5942 255.871H223.647C234.767 255.871 243.788 246.853 243.788 235.738V116.234C243.788 51.9948 191.691 -0.0898438 127.416 -0.0898438ZM194.401 115.589L143.537 158.421C134.357 166.155 120.929 166.155 111.749 158.421L60.8849 115.589C60.8849 79.2409 90.3659 49.7718 126.728 49.7718H128.558C164.92 49.7718 194.401 79.2409 194.401 115.589Z" fill="#6D4AFF"/>
|
||||||
|
<path d="M127.416 -0.0898438C63.1423 -0.0898438 11.0449 51.9864 11.0449 116.234V233.331C11.0449 245.779 21.1405 255.871 33.5942 255.871H223.647C234.767 255.871 243.788 246.853 243.788 235.738V116.234C243.788 51.9948 191.691 -0.0898438 127.416 -0.0898438ZM194.401 115.589L143.537 158.421C134.357 166.155 120.929 166.155 111.749 158.421L60.8849 115.589C60.8849 79.2409 90.3659 49.7718 126.728 49.7718H128.558C164.92 49.7718 194.401 79.2409 194.401 115.589Z" fill="url(#paint0_linear_9588_57903)"/>
|
||||||
|
<g filter="url(#filter0_i_9588_57903)">
|
||||||
|
<path d="M143.572 158.939C138.271 163.23 124.489 169.238 111.766 158.939C99.0439 148.64 72.6401 125.871 61.0285 115.774H61.0868L60.8676 115.59C60.8676 79.2418 90.3367 49.7728 126.684 49.7728H128.513C164.861 49.7728 194.33 79.2418 194.33 115.59L194.111 115.774H194.31V255.872H223.564C234.679 255.872 243.697 246.854 243.697 235.739V116.235C243.697 51.9958 191.62 -0.0888672 127.372 -0.0888672C63.1241 -0.0888672 11.0479 51.9874 11.0479 116.235V123.587L82.9896 185.444C88.2906 190.492 102.224 197.56 115.553 185.444C128.881 173.327 139.786 162.726 143.572 158.939Z" fill="url(#paint1_radial_9588_57903)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_i_9588_57903" x="7.29545" y="-0.0888672" width="236.401" height="266.43" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dx="-3.7524" dy="10.4692"/>
|
||||||
|
<feGaussianBlur stdDeviation="28.143"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.462745 0 0 0 0 0.337255 0 0 0 0 1 0 0 0 0.24 0"/>
|
||||||
|
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_9588_57903"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear_9588_57903" x1="19.3784" y1="285.405" x2="54.2022" y2="186.949" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#28B0E8"/>
|
||||||
|
<stop offset="1" stop-color="#C5B7FF" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="paint1_radial_9588_57903" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(229.979 277.075) rotate(-138.034) scale(294.445 240.743)">
|
||||||
|
<stop stop-color="#E2DBFF"/>
|
||||||
|
<stop offset="1" stop-color="#6D4AFF"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip0_9588_57903">
|
||||||
|
<rect width="256" height="256" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
22
dist/bridgeMacOS.svg
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 260 260" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g>
|
||||||
|
<g transform="matrix(1.27944,0,0,1.35453,-34.9539,-16.0513)">
|
||||||
|
<path d="M40,62.391C40,38.979 58.979,20 82.391,20L177.609,20C201.021,20 220,38.979 220,62.391L220,157.609C220,181.021 201.021,200 177.609,200L82.391,200C58.979,200 40,181.021 40,157.609L40,62.391Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.41874,0,0,1.41874,-55.214,-18.9171)">
|
||||||
|
<path d="M129.748,48.657C101.923,48.657 79.369,71.21 79.369,99.035L79.369,149.747C79.369,155.139 83.74,159.509 89.131,159.509L171.407,159.509C176.22,159.509 180.126,155.604 180.126,150.79L180.126,99.035C180.126,71.214 157.572,48.657 129.748,48.657ZM158.746,98.755L136.726,117.305C132.752,120.655 126.939,120.655 122.965,117.305L100.945,98.755C100.945,83.014 113.708,70.251 129.45,70.251L130.242,70.251C145.983,70.251 158.746,83.014 158.746,98.755Z" style="fill:rgb(109,74,255);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.41874,0,0,1.41874,-55.214,-18.9171)">
|
||||||
|
<path d="M129.748,48.657C101.923,48.657 79.369,71.21 79.369,99.035L79.369,149.748C79.369,155.139 83.74,159.509 89.131,159.509L171.407,159.509C176.22,159.509 180.126,155.604 180.126,150.79L180.126,99.035C180.126,71.214 157.572,48.657 129.748,48.657ZM158.746,98.755L136.726,117.305C132.752,120.655 126.939,120.655 122.965,117.305L100.945,98.755C100.945,83.014 113.708,70.251 129.45,70.251L130.242,70.251C145.983,70.251 158.746,83.014 158.746,98.755Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.41874,0,0,1.41874,-55.214,-18.9171)">
|
||||||
|
<path d="M136.764,117.529C134.468,119.388 128.499,121.99 122.989,117.529C117.479,113.069 106.044,103.208 101.015,98.835L101.041,98.835L100.946,98.756C100.946,83.014 113.709,70.251 129.45,70.251L130.242,70.251C145.984,70.251 158.746,83.014 158.746,98.756L158.652,98.835L158.737,98.835L158.737,159.51L171.407,159.51C176.221,159.51 180.126,155.604 180.126,150.79L180.126,99.035C180.126,71.214 157.573,48.657 129.748,48.657C101.923,48.657 79.37,71.211 79.37,99.035L79.37,102.219L110.526,129.008C112.822,131.195 118.857,134.256 124.629,129.008C130.401,123.761 135.124,119.169 136.764,117.529Z" style="fill:url(#_Radial2);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(15.0864,-42.636,42.636,15.0864,82.9769,172.3)"><stop offset="0" style="stop-color:rgb(40,176,232);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(197,183,255);stop-opacity:0"/></linearGradient>
|
||||||
|
<radialGradient id="_Radial2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-94.8157,-85.2706,69.7189,-77.5232,174.186,168.693)"><stop offset="0" style="stop-color:rgb(226,219,255);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(109,74,255);stop-opacity:1"/></radialGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
IDI_ICON1 ICON DISCARDABLE STRINGIZE(ICO_FILE)
|
IDI_ICON1 ICON DISCARDABLE STRINGIZE(ICO_FILE)
|
||||||
|
|
||||||
#define FILE_COMMENTS "The Bridge is an application that runs on your computer in the background and seamlessly encrypts and decrypts your mail as it enters and leaves your computer."
|
#define FILE_COMMENTS "Proton Mail Bridge is a desktop application that runs in the background, encrypting and decrypting messages as they enter and leave your computer."
|
||||||
#define FILE_DESCRIPTION "Proton Mail Bridge"
|
#define FILE_DESCRIPTION "Proton Mail Bridge"
|
||||||
#define INTERNAL_NAME STRINGIZE(EXE_NAME)
|
#define INTERNAL_NAME STRINGIZE(EXE_NAME)
|
||||||
#define PRODUCT_NAME "Proton Mail Bridge for Windows"
|
#define PRODUCT_NAME "Proton Mail Bridge for Windows"
|
||||||
4
dist/proton-bridge.desktop
vendored
@ -3,9 +3,9 @@ Type=Application
|
|||||||
Version=1.1
|
Version=1.1
|
||||||
Name=Proton Mail Bridge
|
Name=Proton Mail Bridge
|
||||||
GenericName=Proton Mail Bridge for Linux
|
GenericName=Proton Mail Bridge for Linux
|
||||||
Comment=The Bridge is an application that runs on your computer in the background and seamlessly encrypts and decrypts your mail as it enters and leaves your computer.
|
Comment=Proton Mail Bridge is a desktop application that runs in the background, encrypting and decrypting messages as they enter and leave your computer.
|
||||||
Icon=protonmail-bridge
|
Icon=protonmail-bridge
|
||||||
Exec=protonmail-bridge
|
Exec=protonmail-bridge
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Office;Email;Network
|
Categories=Office;Email;Network
|
||||||
StartupWMClass=protonmail-bridge
|
StartupWMClass=Proton Mail Bridge
|
||||||
|
|||||||
BIN
dist/raw/mac_icon_128x128.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
dist/raw/mac_icon_128x128@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
dist/raw/mac_icon_16x16.png
vendored
Normal file
|
After Width: | Height: | Size: 715 B |
BIN
dist/raw/mac_icon_16x16@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
dist/raw/mac_icon_256x256.png
vendored
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
dist/raw/mac_icon_256x256@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
dist/raw/mac_icon_32x32.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
dist/raw/mac_icon_32x32@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
dist/raw/mac_icon_512x512.png
vendored
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
dist/raw/mac_icon_512x512@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 494 KiB |
32
dist/raw/win+lin_icon_16x16.svg
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_9588_57915)">
|
||||||
|
<path d="M7.9607 -0.00488281C3.9452 -0.00488281 0.69043 3.24988 0.69043 7.26539V14.5839C0.69043 15.3619 1.32115 15.9926 2.09919 15.9926H13.9727C14.6674 15.9926 15.231 15.429 15.231 14.7344V7.26539C15.231 3.25041 11.9762 -0.00488281 7.9607 -0.00488281ZM12.1456 7.22502L8.96786 9.90202C8.39429 10.3854 7.55543 10.3854 6.98186 9.90202L3.80416 7.22502C3.80416 4.95329 5.64598 3.11147 7.91771 3.11147H8.032C10.3037 3.11147 12.1456 4.95329 12.1456 7.22502Z" fill="#6D4AFF"/>
|
||||||
|
<path d="M7.9607 -0.00488281C3.9452 -0.00488281 0.69043 3.24988 0.69043 7.26539V14.5839C0.69043 15.3619 1.32115 15.9926 2.09919 15.9926H13.9727C14.6674 15.9926 15.231 15.429 15.231 14.7344V7.26539C15.231 3.25041 11.9762 -0.00488281 7.9607 -0.00488281ZM12.1456 7.22502L8.96786 9.90202C8.39429 10.3854 7.55543 10.3854 6.98186 9.90202L3.80416 7.22502C3.80416 4.95329 5.64598 3.11147 7.91771 3.11147H8.032C10.3037 3.11147 12.1456 4.95329 12.1456 7.22502Z" fill="url(#paint0_linear_9588_57915)"/>
|
||||||
|
<g filter="url(#filter0_i_9588_57915)">
|
||||||
|
<path d="M8.97323 9.93257C8.64192 10.2008 7.78051 10.5763 6.98537 9.93257C6.19022 9.28888 4.53998 7.86583 3.81426 7.23476H3.81805L3.80416 7.22306C3.80416 4.95133 5.64598 3.10952 7.91771 3.10952H8.032C10.3037 3.10952 12.1456 4.95133 12.1456 7.22306L12.1317 7.23476H12.1443V15.9907H13.9727C14.6674 15.9907 15.231 15.4271 15.231 14.7324V7.26343C15.231 3.24845 11.9762 -0.00683594 7.9607 -0.00683594C3.9452 -0.00683594 0.69043 3.24793 0.69043 7.26343V7.72306L5.18683 11.5891C5.51814 11.9047 6.38901 12.3464 7.22202 11.5891C8.05502 10.8318 8.73658 10.1692 8.97323 9.93257Z" fill="url(#paint1_radial_9588_57915)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_i_9588_57915" x="0.455905" y="-0.00683594" width="14.7755" height="16.6514" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dx="-0.234525" dy="0.654324"/>
|
||||||
|
<feGaussianBlur stdDeviation="1.75894"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.462745 0 0 0 0 0.337255 0 0 0 0 1 0 0 0 0.24 0"/>
|
||||||
|
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_9588_57915"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear_9588_57915" x1="1.21106" y1="17.8385" x2="3.38824" y2="11.6856" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#28B0E8"/>
|
||||||
|
<stop offset="1" stop-color="#C5B7FF" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="paint1_radial_9588_57915" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(14.3736 17.3159) rotate(-138.034) scale(18.4028 15.0465)">
|
||||||
|
<stop stop-color="#E2DBFF"/>
|
||||||
|
<stop offset="1" stop-color="#6D4AFF"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip0_9588_57915">
|
||||||
|
<rect width="16" height="16" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
32
dist/raw/win+lin_icon_24x24.svg
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_9588_57912)">
|
||||||
|
<path d="M11.8651 -0.0078125C6.09279 -0.0078125 1.41406 4.67091 1.41406 10.4432V20.9636C1.41406 22.082 2.32072 22.9886 3.43915 22.9886H20.5073C21.5059 22.9886 22.3161 22.1785 22.3161 21.1799V10.4432C22.3161 4.67167 17.6374 -0.0078125 11.8651 -0.0078125ZM17.8808 10.3852L13.3129 14.2334C12.4884 14.9282 11.2825 14.9282 10.458 14.2334L5.89005 10.3852C5.89005 7.11956 8.53767 4.47195 11.8033 4.47195H11.9676C15.2332 4.47195 17.8808 7.11956 17.8808 10.3852Z" fill="#6D4AFF"/>
|
||||||
|
<path d="M11.8651 -0.0078125C6.09279 -0.0078125 1.41406 4.67091 1.41406 10.4432V20.9636C1.41406 22.082 2.32072 22.9886 3.43915 22.9886H20.5073C21.5059 22.9886 22.3161 22.1785 22.3161 21.1799V10.4432C22.3161 4.67167 17.6374 -0.0078125 11.8651 -0.0078125ZM17.8808 10.3852L13.3129 14.2334C12.4884 14.9282 11.2825 14.9282 10.458 14.2334L5.89005 10.3852C5.89005 7.11956 8.53767 4.47195 11.8033 4.47195H11.9676C15.2332 4.47195 17.8808 7.11956 17.8808 10.3852Z" fill="url(#paint0_linear_9588_57912)"/>
|
||||||
|
<g filter="url(#filter0_i_9588_57912)">
|
||||||
|
<path d="M13.3213 14.28C12.8451 14.6656 11.6068 15.2053 10.4638 14.28C9.32078 13.3547 6.94856 11.3091 5.90533 10.4019H5.91095L5.89103 10.3852C5.89103 7.11956 8.53864 4.47195 11.8043 4.47195H11.9686C15.2342 4.47195 17.8818 7.11956 17.8818 10.3852L17.8619 10.4019H17.8798V22.9886H20.5083C21.5069 22.9886 22.3171 22.1785 22.3171 21.1799V10.4432C22.3171 4.67167 17.6383 -0.0078125 11.8661 -0.0078125C6.09377 -0.0078125 1.41504 4.67091 1.41504 10.4432V11.1041L7.8784 16.6613C8.35466 17.1149 9.60653 17.7499 10.804 16.6613C12.0014 15.5727 12.9812 14.6202 13.3213 14.28Z" fill="url(#paint1_radial_9588_57912)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_i_9588_57912" x="1.07791" y="-0.0078125" width="21.2395" height="23.9367" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dx="-0.337129" dy="0.940591"/>
|
||||||
|
<feGaussianBlur stdDeviation="2.52847"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.462745 0 0 0 0 0.337255 0 0 0 0 1 0 0 0 0.24 0"/>
|
||||||
|
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_9588_57912"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear_9588_57912" x1="2.16247" y1="25.6421" x2="5.29216" y2="16.7973" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#28B0E8"/>
|
||||||
|
<stop offset="1" stop-color="#C5B7FF" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="paint1_radial_9588_57912" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(21.0847 24.8937) rotate(-138.034) scale(26.454 21.6293)">
|
||||||
|
<stop stop-color="#E2DBFF"/>
|
||||||
|
<stop offset="1" stop-color="#6D4AFF"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip0_9588_57912">
|
||||||
|
<rect width="24" height="24" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
BIN
dist/raw/win+lin_icon_256x256.png
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
32
dist/raw/win+lin_icon_256x256.svg
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_9588_57903)">
|
||||||
|
<path d="M127.416 -0.0898438C63.1423 -0.0898438 11.0449 51.9864 11.0449 116.234V233.331C11.0449 245.779 21.1405 255.871 33.5942 255.871H223.647C234.767 255.871 243.788 246.853 243.788 235.738V116.234C243.788 51.9948 191.691 -0.0898438 127.416 -0.0898438ZM194.401 115.589L143.537 158.421C134.357 166.155 120.929 166.155 111.749 158.421L60.8849 115.589C60.8849 79.2409 90.3659 49.7718 126.728 49.7718H128.558C164.92 49.7718 194.401 79.2409 194.401 115.589Z" fill="#6D4AFF"/>
|
||||||
|
<path d="M127.416 -0.0898438C63.1423 -0.0898438 11.0449 51.9864 11.0449 116.234V233.331C11.0449 245.779 21.1405 255.871 33.5942 255.871H223.647C234.767 255.871 243.788 246.853 243.788 235.738V116.234C243.788 51.9948 191.691 -0.0898438 127.416 -0.0898438ZM194.401 115.589L143.537 158.421C134.357 166.155 120.929 166.155 111.749 158.421L60.8849 115.589C60.8849 79.2409 90.3659 49.7718 126.728 49.7718H128.558C164.92 49.7718 194.401 79.2409 194.401 115.589Z" fill="url(#paint0_linear_9588_57903)"/>
|
||||||
|
<g filter="url(#filter0_i_9588_57903)">
|
||||||
|
<path d="M143.572 158.939C138.271 163.23 124.489 169.238 111.766 158.939C99.0439 148.64 72.6401 125.871 61.0285 115.774H61.0868L60.8676 115.59C60.8676 79.2418 90.3367 49.7728 126.684 49.7728H128.513C164.861 49.7728 194.33 79.2418 194.33 115.59L194.111 115.774H194.31V255.872H223.564C234.679 255.872 243.697 246.854 243.697 235.739V116.235C243.697 51.9958 191.62 -0.0888672 127.372 -0.0888672C63.1241 -0.0888672 11.0479 51.9874 11.0479 116.235V123.587L82.9896 185.444C88.2906 190.492 102.224 197.56 115.553 185.444C128.881 173.327 139.786 162.726 143.572 158.939Z" fill="url(#paint1_radial_9588_57903)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_i_9588_57903" x="7.29545" y="-0.0888672" width="236.401" height="266.43" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dx="-3.7524" dy="10.4692"/>
|
||||||
|
<feGaussianBlur stdDeviation="28.143"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.462745 0 0 0 0 0.337255 0 0 0 0 1 0 0 0 0.24 0"/>
|
||||||
|
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_9588_57903"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear_9588_57903" x1="19.3784" y1="285.405" x2="54.2022" y2="186.949" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#28B0E8"/>
|
||||||
|
<stop offset="1" stop-color="#C5B7FF" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="paint1_radial_9588_57903" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(229.979 277.075) rotate(-138.034) scale(294.445 240.743)">
|
||||||
|
<stop stop-color="#E2DBFF"/>
|
||||||
|
<stop offset="1" stop-color="#6D4AFF"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip0_9588_57903">
|
||||||
|
<rect width="256" height="256" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
32
dist/raw/win+lin_icon_32x32.svg
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_9588_57909)">
|
||||||
|
<path d="M15.9709 -0.0107422C8.19088 -0.0107422 1.88477 6.29537 1.88477 14.0754V28.255C1.88477 29.7625 3.10678 30.9845 4.61423 30.9845H27.6191C28.9651 30.9845 30.0571 29.8925 30.0571 28.5465V14.0754C30.0571 6.29638 23.751 -0.0107422 15.9709 -0.0107422ZM24.0791 13.9972L17.9223 19.1839C16.811 20.1205 15.1857 20.1205 14.0744 19.1839L7.91762 13.9972C7.91762 9.59571 11.4861 6.02719 15.8876 6.02719H16.1091C20.5105 6.02719 24.0791 9.59571 24.0791 13.9972Z" fill="#6D4AFF"/>
|
||||||
|
<path d="M15.9709 -0.0107422C8.19088 -0.0107422 1.88477 6.29537 1.88477 14.0754V28.255C1.88477 29.7625 3.10678 30.9845 4.61423 30.9845H27.6191C28.9651 30.9845 30.0571 29.8925 30.0571 28.5465V14.0754C30.0571 6.29638 23.751 -0.0107422 15.9709 -0.0107422ZM24.0791 13.9972L17.9223 19.1839C16.811 20.1205 15.1857 20.1205 14.0744 19.1839L7.91762 13.9972C7.91762 9.59571 11.4861 6.02719 15.8876 6.02719H16.1091C20.5105 6.02719 24.0791 9.59571 24.0791 13.9972Z" fill="url(#paint0_linear_9588_57909)"/>
|
||||||
|
<g filter="url(#filter0_i_9588_57909)">
|
||||||
|
<path d="M17.9322 19.2467C17.2903 19.7663 15.6213 20.4938 14.0807 19.2467C12.5401 17.9996 9.34279 15.2424 7.9367 14.0197H7.94435L7.91762 13.9972C7.91762 9.59571 11.4861 6.02719 15.8876 6.02719H16.1091C20.5105 6.02719 24.0791 9.59571 24.0791 13.9972L24.0523 14.0197H24.0762V30.9845H27.6191C28.9651 30.9845 30.0571 29.8925 30.0571 28.5465V14.0754C30.0571 6.29638 23.751 -0.0107422 15.9709 -0.0107422C8.19088 -0.0107422 1.88477 6.29537 1.88477 14.0754V14.9662L10.596 22.4563C11.238 23.0676 12.9253 23.9235 14.5392 22.4563C16.1532 20.989 17.4737 19.7052 17.9322 19.2467Z" fill="url(#paint1_radial_9588_57909)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_i_9588_57909" x="1.43037" y="-0.0107422" width="28.6263" height="32.2629" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dx="-0.454392" dy="1.26775"/>
|
||||||
|
<feGaussianBlur stdDeviation="3.40794"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.462745 0 0 0 0 0.337255 0 0 0 0 1 0 0 0 0.24 0"/>
|
||||||
|
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_9588_57909"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear_9588_57909" x1="2.89348" y1="34.5608" x2="7.11177" y2="22.6396" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#28B0E8"/>
|
||||||
|
<stop offset="1" stop-color="#C5B7FF" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="paint1_radial_9588_57909" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(28.396 33.5521) rotate(-138.034) scale(35.6554 29.1525)">
|
||||||
|
<stop stop-color="#E2DBFF"/>
|
||||||
|
<stop offset="1" stop-color="#6D4AFF"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip0_9588_57909">
|
||||||
|
<rect width="32" height="32" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
32
dist/raw/win+lin_icon_48x48.svg
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_9588_57906)">
|
||||||
|
<path d="M23.8921 -0.0166016C11.9792 -0.0166016 2.32324 9.63938 2.32324 21.5523V43.2642C2.32324 45.5724 4.1944 47.4436 6.50263 47.4436H41.728C43.7889 47.4436 45.461 45.7715 45.461 43.7106V21.5523C45.461 9.64093 35.805 -0.0166016 23.8921 -0.0166016ZM36.3074 21.4325L26.8801 29.3744C25.1784 30.8085 22.6898 30.8085 20.9882 29.3744L11.5608 21.4325C11.5608 14.6929 17.025 9.22875 23.7646 9.22875H24.1036C30.8432 9.22875 36.3074 14.6929 36.3074 21.4325Z" fill="#6D4AFF"/>
|
||||||
|
<path d="M23.8921 -0.0166016C11.9792 -0.0166016 2.32324 9.63938 2.32324 21.5523V43.2642C2.32324 45.5724 4.1944 47.4436 6.50263 47.4436H41.728C43.7889 47.4436 45.461 45.7715 45.461 43.7106V21.5523C45.461 9.64093 35.805 -0.0166016 23.8921 -0.0166016ZM36.3074 21.4325L26.8801 29.3744C25.1784 30.8085 22.6898 30.8085 20.9882 29.3744L11.5608 21.4325C11.5608 14.6929 17.025 9.22875 23.7646 9.22875H24.1036C30.8432 9.22875 36.3074 14.6929 36.3074 21.4325Z" fill="url(#paint0_linear_9588_57906)"/>
|
||||||
|
<g filter="url(#filter0_i_9588_57906)">
|
||||||
|
<path d="M26.8954 29.4706C25.9125 30.2663 23.3569 31.3803 20.998 29.4706C18.639 27.561 13.7432 23.3392 11.5902 21.467H11.6017L11.5608 21.4325C11.5608 14.6929 17.025 9.22875 23.7646 9.22875H24.1036C30.8432 9.22875 36.3074 14.6929 36.3074 21.4325L36.2665 21.467H36.3032V47.4436H41.728C43.7889 47.4436 45.461 45.7715 45.461 43.7106V21.5523C45.461 9.64093 35.805 -0.0166016 23.8921 -0.0166016C11.9792 -0.0166016 2.32324 9.63938 2.32324 21.5523V22.9161L15.6622 34.3851C16.6451 35.3212 19.2287 36.6318 21.7 34.3851C24.1713 32.1385 26.1933 30.1727 26.8954 29.4706Z" fill="url(#paint1_radial_9588_57906)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_i_9588_57906" x="1.62747" y="-0.0166016" width="43.8335" height="49.4012" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dx="-0.69577" dy="1.9412"/>
|
||||||
|
<feGaussianBlur stdDeviation="5.21827"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.462745 0 0 0 0 0.337255 0 0 0 0 1 0 0 0 0.24 0"/>
|
||||||
|
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_9588_57906"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear_9588_57906" x1="3.8678" y1="52.9198" x2="10.3269" y2="34.6659" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#28B0E8"/>
|
||||||
|
<stop offset="1" stop-color="#C5B7FF" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="paint1_radial_9588_57906" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(42.9175 51.3752) rotate(-138.034) scale(54.5959 44.6386)">
|
||||||
|
<stop stop-color="#E2DBFF"/>
|
||||||
|
<stop offset="1" stop-color="#6D4AFF"/>
|
||||||
|
</radialGradient>
|
||||||
|
<clipPath id="clip0_9588_57906">
|
||||||
|
<rect width="48" height="48" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
## First login and sync
|
## First login and sync
|
||||||
|
|
||||||
When user logs in to the bridge for the first time, immediatelly starts the first sync.
|
When user logs in to the bridge for the first time, immediately starts the first sync.
|
||||||
First sync downloads all headers of all e-mails and creates database to have proper UIDs
|
First sync downloads all headers of all e-mails and creates database to have proper UIDs
|
||||||
and indexes for IMAP. See [database](database.md) for more information.
|
and indexes for IMAP. See [database](database.md) for more information.
|
||||||
|
|
||||||
By default, whenever it's possible, sync downloads only all e-mails maiblox which already
|
By default, whenever it's possible, sync downloads only all e-mails maiblox which already
|
||||||
have list of labels so we can construct all mailboxes (inbox, sent, trash, custom folders
|
have list of labels so we can construct all mailboxes (inbox, sent, trash, custom folders
|
||||||
and lables) without need to download each e-mail headers many times.
|
and labels) without need to download each e-mail headers many times.
|
||||||
|
|
||||||
Note that we need to download also bodies to calculate size of the e-mail and set proper
|
Note that we need to download also bodies to calculate size of the e-mail and set proper
|
||||||
content type (clients uses content type for guess if e-mail contains attachment)--but only
|
content type (clients uses content type for guess if e-mail contains attachment)--but only
|
||||||
@ -22,7 +22,7 @@ client right after adding account.
|
|||||||
|
|
||||||
When account is added to client, client start the sync. This sync will ask Bridge app
|
When account is added to client, client start the sync. This sync will ask Bridge app
|
||||||
for all headers (done quickly) and then starts to download all bodies and attachment.
|
for all headers (done quickly) and then starts to download all bodies and attachment.
|
||||||
Unfortunatelly for some e-mail more than once if the same e-mail is in more mailboxes
|
Unfortunately for some e-mail more than once if the same e-mail is in more mailboxes
|
||||||
(e.g. inbox and all mail)--there is no way to tell over IMAP it's the same message.
|
(e.g. inbox and all mail)--there is no way to tell over IMAP it's the same message.
|
||||||
|
|
||||||
After successful login of client to IMAP, Bridge starts event loop. That periodicly ask
|
After successful login of client to IMAP, Bridge starts event loop. That periodicly ask
|
||||||
@ -37,7 +37,7 @@ sequenceDiagram
|
|||||||
Note right of B: Set up PM account<br/>by user
|
Note right of B: Set up PM account<br/>by user
|
||||||
|
|
||||||
loop First sync
|
loop First sync
|
||||||
B ->> S: Fetch body and attachements
|
B ->> S: Fetch body and attachments
|
||||||
Note right of B: Build local database<br/>(e-mail UIDs)
|
Note right of B: Build local database<br/>(e-mail UIDs)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -58,8 +58,8 @@ sequenceDiagram
|
|||||||
C ->> B: IMAP SELECT directory
|
C ->> B: IMAP SELECT directory
|
||||||
C ->> B: IMAP SEARCH e-mails UIDs
|
C ->> B: IMAP SEARCH e-mails UIDs
|
||||||
C ->> B: IMAP FETCH of e-mail UID
|
C ->> B: IMAP FETCH of e-mail UID
|
||||||
B ->> S: Fetch body and attachements
|
B ->> S: Fetch body and attachments
|
||||||
Note right of B: Decrypt message<br/>and attachement
|
Note right of B: Decrypt message<br/>and attachment
|
||||||
B ->> C: IMAP response
|
B ->> C: IMAP response
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
# Update mechanism of Bridge
|
# Update mechanism of Bridge
|
||||||
|
|
||||||
There are mulitple options how to change version of application:
|
There are multiple options how to change version of application:
|
||||||
* Automatic in-app update
|
* Automatic in-app update
|
||||||
* Manual in-app update
|
* Manual in-app update
|
||||||
* Manual install
|
* Manual install
|
||||||
|
|
||||||
In-app update ends with restarting bridge into new version. Automatic in-app
|
In-app update ends with restarting bridge into new version. Automatic in-app
|
||||||
update is downloading, verifying and installing the new version immediatelly
|
update is downloading, verifying and installing the new version immediately
|
||||||
without user confirmation. For manual in-app update user needs to confirm first.
|
without user confirmation. For manual in-app update user needs to confirm first.
|
||||||
Update is done from special update file published on website.
|
Update is done from special update file published on website.
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ The bridge is installed and executed differently for given OS:
|
|||||||
|
|
||||||
* macOS app does not use launcher
|
* macOS app does not use launcher
|
||||||
* No launcher, only one executable
|
* No launcher, only one executable
|
||||||
* In-App udpate replaces the bridge files in installation path directly
|
* In-App update replaces the bridge files in installation path directly
|
||||||
|
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|||||||
1
extern/vcpkg
vendored
Submodule
181
go.mod
@ -1,85 +1,128 @@
|
|||||||
module github.com/ProtonMail/proton-bridge
|
module github.com/ProtonMail/proton-bridge/v3
|
||||||
|
|
||||||
go 1.15
|
go 1.20
|
||||||
|
|
||||||
// These dependencies are `replace`d below, so the version numbers should be ignored.
|
|
||||||
// They are in a separate require block to highlight this.
|
|
||||||
require (
|
require (
|
||||||
|
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||||
|
github.com/Masterminds/semver/v3 v3.2.0
|
||||||
|
github.com/ProtonMail/gluon v0.17.1-0.20231206152152-caaf10897f9e
|
||||||
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20231130083229-e8aa47d7a366
|
||||||
|
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton
|
||||||
|
github.com/PuerkitoBio/goquery v1.8.1
|
||||||
|
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||||
|
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37
|
||||||
|
github.com/bradenaw/juniper v0.12.0
|
||||||
|
github.com/cucumber/godog v0.12.5
|
||||||
|
github.com/cucumber/messages-go/v16 v16.0.1
|
||||||
github.com/docker/docker-credential-helpers v0.6.3
|
github.com/docker/docker-credential-helpers v0.6.3
|
||||||
github.com/emersion/go-imap v1.0.6
|
github.com/elastic/go-sysinfo v1.11.2-0.20231129083954-35e55cd2a542
|
||||||
github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998 // indirect
|
github.com/emersion/go-imap v1.2.1
|
||||||
|
github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
||||||
|
github.com/emersion/go-message v0.16.0
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead
|
||||||
|
github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d
|
||||||
|
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3
|
||||||
|
github.com/fatih/color v1.13.0
|
||||||
|
github.com/getsentry/sentry-go v0.15.0
|
||||||
|
github.com/go-resty/resty/v2 v2.7.0
|
||||||
|
github.com/godbus/dbus v4.1.0+incompatible
|
||||||
|
github.com/golang/mock v1.6.0
|
||||||
|
github.com/google/go-cmp v0.5.9
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
|
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20220711133428-7de61946b173
|
||||||
|
github.com/keybase/go-keychain v0.0.0
|
||||||
|
github.com/miekg/dns v1.1.50
|
||||||
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/pkg/profile v1.7.0
|
||||||
|
github.com/sirupsen/logrus v1.9.2
|
||||||
|
github.com/stretchr/testify v1.8.3
|
||||||
|
github.com/urfave/cli/v2 v2.24.4
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.3.5
|
||||||
|
go.uber.org/goleak v1.2.1
|
||||||
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||||
|
golang.org/x/net v0.17.0
|
||||||
|
golang.org/x/sys v0.13.0
|
||||||
|
golang.org/x/text v0.13.0
|
||||||
|
google.golang.org/grpc v1.56.3
|
||||||
|
google.golang.org/protobuf v1.30.0
|
||||||
|
howett.net/plist v1.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.1.0
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 // indirect
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab
|
github.com/ProtonMail/go-srp v0.0.7 // indirect
|
||||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
|
||||||
github.com/ProtonMail/go-rfc5322 v0.8.0
|
|
||||||
github.com/ProtonMail/go-srp v0.0.1
|
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
|
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.4.1
|
|
||||||
github.com/PuerkitoBio/goquery v1.5.1
|
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
||||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc
|
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||||
github.com/chzyer/logex v1.1.10 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
github.com/cucumber/godog v0.12.1
|
github.com/chzyer/test v1.0.0 // indirect
|
||||||
github.com/cucumber/messages-go/v16 v16.0.1
|
github.com/cloudflare/circl v1.3.3 // indirect
|
||||||
github.com/elastic/go-sysinfo v1.7.1
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
|
github.com/cronokirby/saferith v0.33.0 // indirect
|
||||||
|
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
|
||||||
|
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/elastic/go-windows v1.0.1 // indirect
|
github.com/elastic/go-windows v1.0.1 // indirect
|
||||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
|
||||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
|
github.com/felixge/fgprof v0.9.3 // indirect
|
||||||
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c
|
|
||||||
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26
|
|
||||||
github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b
|
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
|
||||||
github.com/emersion/go-smtp v0.14.0
|
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594
|
|
||||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 // indirect
|
|
||||||
github.com/fatih/color v1.9.0
|
|
||||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||||
github.com/getsentry/sentry-go v0.12.0
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/go-resty/resty/v2 v2.6.0
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/godbus/dbus v4.1.0+incompatible
|
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||||
github.com/golang/mock v1.4.4
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/google/go-cmp v0.5.5
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/google/uuid v1.1.1
|
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.0
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
|
github.com/gofrs/uuid v4.3.0+incompatible // indirect
|
||||||
github.com/keybase/go-keychain v0.0.0
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||||
github.com/miekg/dns v1.1.41
|
github.com/hashicorp/go-memdb v1.3.3 // indirect
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce
|
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.4 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/sirupsen/logrus v1.7.0
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.2 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
github.com/urfave/cli/v2 v2.2.0
|
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
|
||||||
github.com/vmihailenco/msgpack/v5 v5.1.3
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
go.etcd.io/bbolt v1.3.6
|
golang.org/x/crypto v0.14.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
|
golang.org/x/sync v0.2.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
golang.org/x/text v0.3.7
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||||
howett.net/plist v1.0.0 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0
|
github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0
|
||||||
github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac
|
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7
|
||||||
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753
|
github.com/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865
|
||||||
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57
|
github.com/go-resty/resty/v2 => github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a
|
||||||
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe
|
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20230517073537-fc1740a83768
|
||||||
)
|
)
|
||||||
|
|||||||
602
go.sum
@ -11,218 +11,232 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
|
|||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 h1:j9HaafapDbPbGRDku6e/HRs6KBMcKHiWcm1/9Sbxnl4=
|
fyne.io/fyne v1.4.2/go.mod h1:xL4c3WmpE/Tvz5CEm5vqsaizU/EeOCm9DYlL2GtTSiM=
|
||||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
|
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 h1:l6surSnJ3RP4qA1qmKJ+hQn3UjytosdoG27WGjrDlVs=
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557/go.mod h1:sTrmvD/TxuypdOERsDOS7SndZg0rzzcCi1b6wQMXUYM=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||||
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
|
github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a h1:eQO/GF/+H8/9udc9QAgieFr+jr1tjXlJo35RAhsUbWY=
|
||||||
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||||
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
|
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||||
|
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57 h1:pHA4K54ifoogVLunGGHi3xyF5Nz4x+Uh3dJuy3NwGQQ=
|
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||||
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
||||||
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc=
|
github.com/ProtonMail/gluon v0.17.1-0.20231206152152-caaf10897f9e h1:kHmSOTxynSip1WJvwZTFOGJPVfI42e/I8bDzDjLK7aM=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
github.com/ProtonMail/gluon v0.17.1-0.20231206152152-caaf10897f9e/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab h1:5FiL/TCaiKCss/BLMIACDxxadYrx767l9kh0qYX+sLQ=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
||||||
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac h1:2xU3QncAiS/W3UlWZTkbNKW5WkLzk6Egl1T0xX+sbjs=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
|
||||||
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:5koQozTDELymYOyFbQ/VSubexAEXzDR8qGM5mO8GRdw=
|
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/DyZ/qGfMT9htAT7HxqIEbZHsatsx+m8AoV6fc=
|
||||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
|
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||||
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753 h1:I8IsYA297x0QLU80G5I6aLYUu3JYNSpo8j5fkXtFDW0=
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||||
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20231130083229-e8aa47d7a366 h1:W9P5GdDnuGkB3tbzKnXmUrTjIs6zk/K+4lpPTWzsoRE=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20231130083229-e8aa47d7a366/go.mod h1:t+hb0BfkmZ9fpvzVRpHC7limoowym6ln/j0XL9a8DDw=
|
||||||
github.com/ProtonMail/go-rfc5322 v0.8.0 h1:7emrf75n3CDIduQflx7aT1nJa5h/kGsiFKUYX/+IAkU=
|
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
|
||||||
github.com/ProtonMail/go-rfc5322 v0.8.0/go.mod h1:BwpTbkJxkMGkc+pC84AXZnwuWOisEULBpfPIyIKS/Us=
|
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||||
github.com/ProtonMail/go-srp v0.0.1 h1:J0O9Zb5XTC6iDrB7feH41cu+TUEB+l7uHctXIK6oS2o=
|
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
||||||
github.com/ProtonMail/go-srp v0.0.1/go.mod h1:Uvv5cqSGCs8MTZ8sbKiCkBnaB6/OA3eq2mc77tl2VVA=
|
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
|
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton h1:8tqHYM6IGsdEc6Vxf1TWiwpHNj8yIEQNACPhxsDagrk=
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
|
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton/go.mod h1:omVkSsfPAhmptzPF/piMXb16wKIWUvVhZbVW7sJKh0A=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.4.1 h1:b3El0zabaKi73u4sRnb3hOOUczuKuYpN8wnp7wRsZSc=
|
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.4.1/go.mod h1:RFjoVjfhV8f78tjz/fLrp/OXkugL3QmWsiJq/fsQYA4=
|
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
|
|
||||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
|
||||||
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
|
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
|
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc h1:mZca0/HZ/XWXP9txkfdl2GH6mUzBqAlyJz3u5Lg8fuA=
|
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37 h1:28uU3TtuvQ6KRndxg9TrC868jBWmSKgh0GTXkACCXmA=
|
||||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc/go.mod h1:qqsTQiwdyqxU05iDCsi0oN3P4nrVxAmn8xCtODDSf/U=
|
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37/go.mod h1:6AXRstqK+32jeFmw89QGL2748+dj34Av4xc/I9oo9BY=
|
||||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||||
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c h1:j/C2kxPfyE0d87/ggAjIsCV5Cdkqmjb+O0W8W+1J+IY=
|
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||||
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
|
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||||
|
github.com/bradenaw/juniper v0.12.0 h1:Q/7icpPQD1nH/La5DobQfNEtwyrBSiSu47jOQx7lJEM=
|
||||||
|
github.com/bradenaw/juniper v0.12.0/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI=
|
||||||
|
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
|
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
|
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||||
|
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||||
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||||
|
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||||
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
|
||||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
|
||||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
github.com/cronokirby/saferith v0.31.0 h1:TIlhldetKLeGAb19bZvWiuwQEzfzwSPthDEyJ9Ah8xs=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/cronokirby/saferith v0.31.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
|
github.com/cronokirby/saferith v0.33.0 h1:TgoQlfsD4LIwx71+ChfRcIpjkw+RPOapDEVxa+LhwLo=
|
||||||
|
github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
|
||||||
github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE=
|
github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE=
|
||||||
github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw=
|
github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw=
|
||||||
github.com/cucumber/godog v0.12.1 h1:IhWVYFKDReM5WsuA9AuRLRPWOyvFNO9UBUKrNfLPais=
|
github.com/cucumber/godog v0.12.5 h1:FZIy6VCfMbmGHts9qd6UjBMT9abctws/pQYO/ZcwOVs=
|
||||||
github.com/cucumber/godog v0.12.1/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc=
|
github.com/cucumber/godog v0.12.5/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc=
|
||||||
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||||
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
|
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
|
||||||
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||||
github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe h1:KRj3wdvA9yE92prNmOjS7x5DOqoyjxqdE30qnrmTasc=
|
github.com/cuthix/go-keychain v0.0.0-20230517073537-fc1740a83768 h1:Jrcoxtrk4qpuzKIYPlEkjIK0M+bABs0oW2QzrOuwlzk=
|
||||||
github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe/go.mod h1:ZoZU1fnBy3mOLWr3Pg+Y2+nTKtu6ypDte2kZg9HvSwY=
|
github.com/cuthix/go-keychain v0.0.0-20230517073537-fc1740a83768/go.mod h1:ZoZU1fnBy3mOLWr3Pg+Y2+nTKtu6ypDte2kZg9HvSwY=
|
||||||
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
|
|
||||||
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
||||||
|
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
||||||
|
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||||
github.com/elastic/go-sysinfo v1.7.1 h1:Wx4DSARcKLllpKT2TnFVdSUJOsybqMYCNQZq1/wO+s0=
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
github.com/elastic/go-sysinfo v1.7.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
|
github.com/elastic/go-sysinfo v1.11.2-0.20231129083954-35e55cd2a542 h1:IFTm6NBbfSgZCaeEzorQhH4T7ZERl4j+1u7oXWzmJcM=
|
||||||
|
github.com/elastic/go-sysinfo v1.11.2-0.20231129083954-35e55cd2a542/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ=
|
||||||
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
||||||
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
|
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
|
||||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a h1:bMdSPm6sssuOFpIaveu3XGAijMS3Tq2S3EqFZmZxidc=
|
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
||||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a/go.mod h1:ikgISoP7pRAolqsVP64yMteJa2FIpS6ju88eBT6K1yQ=
|
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342 h1:5p1t3e1PomYgLWwEwhwEU5kVBwcyAcVrOpexv8AeZx0=
|
github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:43mBoVwooyLm1+1YVf5nvn1pSFWhw7rOpcrp1Jg/qk0=
|
||||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
|
github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:sPwp0FFboaK/bxsrUz1lNrDMUCsZUsKC5YuM4uRVRVs=
|
||||||
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c h1:khcEdu1yFiZjBgi7gGnQiLhpSgghJ0YTnKD0l4EUqqc=
|
|
||||||
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c/go.mod h1:iApyhIQBiU4XFyr+3kdJyyGqle82TbQyuP2o+OZHrV0=
|
|
||||||
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26 h1:FiSb8+XBQQSkcX3ubr+1tAtlRJBYaFmRZqOAweZ9Wy8=
|
|
||||||
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26/go.mod h1:+gnnZx3Mg3MnCzZrv0eZdp5puxXQUgGT/6N6L7ShKfM=
|
|
||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkXlA=
|
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
|
||||||
github.com/emersion/go-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 h1:n9qx98xiS5V4x2WIpPC2rr9mUM5ri9r/YhCEKbhCHro=
|
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 h1:hQ1wTMaKcGfobYRT88RM8NFNyX+IQHvagkm/tqViU98=
|
||||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5/go.mod h1:WIi9g8OKJQHXtQbx7GExlo6UAFaui9WDMYabJ+Be4WI=
|
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
|
||||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||||
|
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
|
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
|
||||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
|
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk=
|
github.com/fyne-io/mobile v0.1.2-0.20201127155338-06aeb98410cc/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
|
||||||
github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
|
github.com/fyne-io/mobile v0.1.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
|
github.com/getsentry/sentry-go v0.15.0 h1:CP9bmA7pralrVUedYZsmIHWpq/pBtXTSew7xvVpfLaA=
|
||||||
|
github.com/getsentry/sentry-go v0.15.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
|
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||||
|
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
|
||||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
|
||||||
|
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||||
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU=
|
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g=
|
github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g=
|
||||||
|
github.com/hashicorp/go-memdb v1.3.3 h1:oGfEWrFuxtIUF3W2q/Jzt6G85TrMk9ey6XfYLvVe1Wo=
|
||||||
|
github.com/hashicorp/go-memdb v1.3.3/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg=
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
@ -230,7 +244,6 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
|
|||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
@ -241,82 +254,61 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
|||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
|
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||||
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
|
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba h1:QFQpJdgbON7I0jr2hYW7Bs+XV0qjc3d5tZoDnRFnqTg=
|
||||||
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
|
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||||
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
|
github.com/jeandeaual/go-locale v0.0.0-20220711133428-7de61946b173 h1:jOONCXyzHWM+ukp+weX77o//U3pMeOj62CNxChJLxIU=
|
||||||
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
|
github.com/jeandeaual/go-locale v0.0.0-20220711133428-7de61946b173/go.mod h1:uO/uctjf8AcWhNfp5Ili6oPtyFrAoQXEtVY3N798VkQ=
|
||||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc=
|
|
||||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
|
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
|
||||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
|
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
|
||||||
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
|
|
||||||
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
|
|
||||||
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
|
|
||||||
github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
|
|
||||||
github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
|
|
||||||
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d h1:gVjhBCfVGl32RIBooOANzfw+0UqX8HU+yPlMv8vypcg=
|
|
||||||
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d/go.mod h1:W6EbaYmb4RldPn0N3gvVHjY1wmU59kbymhW9NATWhwY=
|
|
||||||
github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621 h1:aMQ7pA4f06yOVXSulygyGvy4xA94fyzjUGs0iqQdMOI=
|
|
||||||
github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621/go.mod h1:enrU/ug069Om7vWxuFE6nikLI2BZNwevMiGSo43Kt5w=
|
|
||||||
github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE=
|
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
|
|
||||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
@ -326,32 +318,35 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
|
|||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
github.com/nicksnyder/go-i18n/v2 v2.1.1/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
|
||||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
|
||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758=
|
|
||||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
|
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||||
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||||
|
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
@ -362,31 +357,25 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:
|
|||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285 h1:d54EL9l+XteliUfUCGsEwwuk65dmmxX85VXF+9T6+50=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285/go.mod h1:fxIDly1xtudczrZeOOlfaUvd2OPb2qZAPuWdU2BsBTk=
|
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||||
|
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
|
||||||
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
|
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
@ -395,92 +384,84 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
|
|||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
|
||||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
|
||||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||||
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
|
|
||||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
|
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d h1:hAZyEG2swPRWjF0kqqdGERXUazYnRJdAk4a58f14z7Y=
|
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
|
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d h1:AJRoBel/g9cDS+yE8BcN3E+TDD/xNAguG21aoR8DAIE=
|
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
|
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
|
github.com/urfave/cli/v2 v2.24.4 h1:0gyJJEBYtCV87zI/x2nZCPyDxD51K6xM8SkwjHFCNEU=
|
||||||
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
github.com/urfave/cli/v2 v2.24.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||||
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
|
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
|
||||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
|
||||||
github.com/vmihailenco/msgpack/v5 v5.1.3 h1:FwC9KPjyW8OqTUqMt6rQw9y50vA2cTLXPKCcBCRbQgg=
|
|
||||||
github.com/vmihailenco/msgpack/v5 v5.1.3/go.mod h1:C5gboKD0TJPqWDTVTtrQNfRbiBwHZGo8UTqP/9/XvLI=
|
|
||||||
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
|
|
||||||
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a h1:DxppxFKRqJ8WD6oJ3+ZXKDY0iMONQDl5UTg2aTyHh8k=
|
||||||
|
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a/go.mod h1:NREvu3a57BaK0R1+ztrEzHWiZAihohNLQ6trPxlIqZI=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
|
||||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
|
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||||
|
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||||
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@ -490,15 +471,15 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
|
|||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -506,23 +487,26 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r
|
|||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20211008194852-3b03d305991f h1:1scJEYZBaF48BaG6tYbtxmLcXqwYGSfGcMoStTqkkIw=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -532,90 +516,107 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||||
|
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
|
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
@ -634,9 +635,17 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
|||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||||
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||||
|
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@ -644,29 +653,24 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
|
||||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
|
||||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|
||||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
|
||||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 945 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 25 KiB |
@ -1,85 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Package api provides HTTP API of the Bridge.
|
|
||||||
//
|
|
||||||
// API endpoints:
|
|
||||||
// * /focus, see focusHandler
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
log = logrus.WithField("pkg", "api") //nolint:gochecknoglobals
|
|
||||||
)
|
|
||||||
|
|
||||||
type apiServer struct {
|
|
||||||
host string
|
|
||||||
settings *settings.Settings
|
|
||||||
eventListener listener.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAPIServer returns prepared API server struct.
|
|
||||||
func NewAPIServer(settings *settings.Settings, eventListener listener.Listener) *apiServer { //nolint:revive
|
|
||||||
return &apiServer{
|
|
||||||
host: bridge.Host,
|
|
||||||
settings: settings,
|
|
||||||
eventListener: eventListener,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starts the server.
|
|
||||||
func (api *apiServer) ListenAndServe() {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/focus", wrapper(api, focusHandler))
|
|
||||||
|
|
||||||
addr := api.getAddress()
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
Handler: mux,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("API listening at ", addr)
|
|
||||||
if err := server.ListenAndServe(); err != nil {
|
|
||||||
api.eventListener.Emit(events.ErrorEvent, "API failed: "+err.Error())
|
|
||||||
log.Error("API failed: ", err)
|
|
||||||
}
|
|
||||||
defer server.Close() //nolint:errcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *apiServer) getAddress() string {
|
|
||||||
port := api.settings.GetInt(settings.APIPortKey)
|
|
||||||
newPort := ports.FindFreePortFrom(port)
|
|
||||||
if newPort != port {
|
|
||||||
api.settings.SetInt(settings.APIPortKey, newPort)
|
|
||||||
}
|
|
||||||
return getAPIAddress(api.host, newPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAPIAddress(host string, port int) string {
|
|
||||||
return fmt.Sprintf("%s:%d", host, port)
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
|
||||||
)
|
|
||||||
|
|
||||||
// httpHandler with Go's Response and Request.
|
|
||||||
type httpHandler func(http.ResponseWriter, *http.Request)
|
|
||||||
|
|
||||||
// handler with our context.
|
|
||||||
type handler func(handlerContext) error
|
|
||||||
|
|
||||||
type handlerContext struct {
|
|
||||||
req *http.Request
|
|
||||||
resp http.ResponseWriter
|
|
||||||
eventListener listener.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapper(api *apiServer, callback handler) httpHandler {
|
|
||||||
return func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
ctx := handlerContext{
|
|
||||||
req: req,
|
|
||||||
resp: w,
|
|
||||||
eventListener: api.eventListener,
|
|
||||||
}
|
|
||||||
err := callback(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("API callback of ", req.URL, " failed: ", err)
|
|
||||||
http.Error(w, err.Error(), 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
|
||||||
)
|
|
||||||
|
|
||||||
// focusHandler should be called from other instances (attempt to start bridge
|
|
||||||
// for the second time) to get focus in the currently running instance.
|
|
||||||
func focusHandler(ctx handlerContext) error {
|
|
||||||
log.Info("Focus from other instance")
|
|
||||||
ctx.eventListener.Emit(events.SecondInstanceEvent, "")
|
|
||||||
fmt.Fprintf(ctx.resp, "OK")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckOtherInstanceAndFocus is helper for new instances to check if there is
|
|
||||||
// already a running instance and get it's focus.
|
|
||||||
func CheckOtherInstanceAndFocus(port int) error {
|
|
||||||
addr := getAPIAddress(bridge.Host, port)
|
|
||||||
resp, err := (&http.Client{}).Get("http://" + addr + "/focus")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close() //nolint:errcheck
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
log.Error("Focus error: ", resp.StatusCode)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
507
internal/app/app.go
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/gluon/async"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/cookies"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/crash"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/restarter"
|
||||||
|
"github.com/pkg/profile"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Visible flags.
|
||||||
|
const (
|
||||||
|
flagCPUProfile = "cpu-prof"
|
||||||
|
flagCPUProfileShort = "p"
|
||||||
|
|
||||||
|
flagTraceProfile = "trace-prof"
|
||||||
|
flagTraceProfileShort = "t"
|
||||||
|
|
||||||
|
flagMemProfile = "mem-prof"
|
||||||
|
flagMemProfileShort = "m"
|
||||||
|
|
||||||
|
flagLogLevel = "log-level"
|
||||||
|
flagLogLevelShort = "l"
|
||||||
|
|
||||||
|
flagGRPC = "grpc"
|
||||||
|
flagGRPCShort = "g"
|
||||||
|
|
||||||
|
flagCLI = "cli"
|
||||||
|
flagCLIShort = "c"
|
||||||
|
|
||||||
|
flagNonInteractive = "noninteractive"
|
||||||
|
flagNonInteractiveShort = "n"
|
||||||
|
|
||||||
|
flagLogIMAP = "log-imap"
|
||||||
|
flagLogSMTP = "log-smtp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hidden flags.
|
||||||
|
const (
|
||||||
|
flagLauncher = "launcher"
|
||||||
|
flagNoWindow = "no-window"
|
||||||
|
flagParentPID = "parent-pid"
|
||||||
|
flagSoftwareRenderer = "software-renderer"
|
||||||
|
flagSessionID = "session-id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
appUsage = "Proton Mail IMAP and SMTP Bridge"
|
||||||
|
appShortName = "bridge"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New() *cli.App {
|
||||||
|
app := cli.NewApp()
|
||||||
|
|
||||||
|
app.Name = constants.FullAppName
|
||||||
|
app.Usage = appUsage
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagCPUProfile,
|
||||||
|
Aliases: []string{flagCPUProfileShort},
|
||||||
|
Usage: "Generate CPU profile",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagTraceProfile,
|
||||||
|
Aliases: []string{flagTraceProfileShort},
|
||||||
|
Usage: "Generate Trace profile",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagMemProfile,
|
||||||
|
Aliases: []string{flagMemProfileShort},
|
||||||
|
Usage: "Generate memory profile",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flagLogLevel,
|
||||||
|
Aliases: []string{flagLogLevelShort},
|
||||||
|
Usage: "Set the log level (one of panic, fatal, error, warn, info, debug)",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagGRPC,
|
||||||
|
Aliases: []string{flagGRPCShort},
|
||||||
|
Usage: "Start the gRPC service",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagCLI,
|
||||||
|
Aliases: []string{flagCLIShort},
|
||||||
|
Usage: "Start the command line interface",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagNonInteractive,
|
||||||
|
Aliases: []string{flagNonInteractiveShort},
|
||||||
|
Usage: "Start the app in non-interactive mode",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flagLogIMAP,
|
||||||
|
Usage: "Enable logging of IMAP communications (all|client|server) (may contain decrypted data!)",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagLogSMTP,
|
||||||
|
Usage: "Enable logging of SMTP communications (may contain decrypted data!)",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hidden flags
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagNoWindow,
|
||||||
|
Usage: "Don't show window after start",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flagLauncher,
|
||||||
|
Usage: "The launcher used to start the app",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: flagParentPID,
|
||||||
|
Usage: "Process ID of the parent",
|
||||||
|
Hidden: true,
|
||||||
|
Value: -1,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagSoftwareRenderer, // This flag is ignored by bridge, but should be passed to launcher in case of restart, so it need to be accepted by the CLI parser.
|
||||||
|
Usage: "GUI is using software renderer",
|
||||||
|
Hidden: true,
|
||||||
|
Value: false,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flagSessionID,
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Action = run
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(c *cli.Context) error {
|
||||||
|
// Get the current bridge version.
|
||||||
|
version, err := semver.NewVersion(constants.Version)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a user agent that will be used for all requests.
|
||||||
|
identifier := useragent.New()
|
||||||
|
|
||||||
|
// Create a new Sentry client that will be used to report crashes etc.
|
||||||
|
reporter := sentry.NewReporter(constants.FullAppName, identifier)
|
||||||
|
|
||||||
|
// Determine the exe that should be used to restart/autostart the app.
|
||||||
|
// By default, this is the launcher, if used. Otherwise, we try to get
|
||||||
|
// the current exe, and fall back to os.Args[0] if that fails.
|
||||||
|
var exe string
|
||||||
|
|
||||||
|
if launcher := c.String(flagLauncher); launcher != "" {
|
||||||
|
exe = launcher
|
||||||
|
} else if executable, err := os.Executable(); err == nil {
|
||||||
|
exe = executable
|
||||||
|
} else {
|
||||||
|
exe = os.Args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var logCloser io.Closer
|
||||||
|
defer func() {
|
||||||
|
_ = logging.Close(logCloser)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Restart the app if requested.
|
||||||
|
err = withRestarter(exe, func(restarter *restarter.Restarter) error {
|
||||||
|
// Handle crashes with various actions.
|
||||||
|
return withCrashHandler(restarter, reporter, func(crashHandler *crash.Handler, quitCh <-chan struct{}) error {
|
||||||
|
migrationErr := migrateOldVersions()
|
||||||
|
|
||||||
|
// Run with profiling if requested.
|
||||||
|
return withProfiler(c, func() error {
|
||||||
|
// Load the locations where we store our files.
|
||||||
|
return WithLocations(func(locations *locations.Locations) error {
|
||||||
|
// Migrate the keychain helper.
|
||||||
|
if err := migrateKeychainHelper(locations); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to migrate keychain helper")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize logging.
|
||||||
|
return withLogging(c, crashHandler, locations, func(closer io.Closer) error {
|
||||||
|
logCloser = closer
|
||||||
|
|
||||||
|
// If there was an error during migration, log it now.
|
||||||
|
if migrationErr != nil {
|
||||||
|
logrus.WithError(migrationErr).Error("Failed to migrate old app data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we are the only instance running.
|
||||||
|
settings, err := locations.ProvideSettingsPath()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to get settings path")
|
||||||
|
}
|
||||||
|
|
||||||
|
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
|
||||||
|
// Look for available keychains
|
||||||
|
return WithKeychainList(func(keychains *keychain.List) error {
|
||||||
|
// Unlock the encrypted vault.
|
||||||
|
return WithVault(locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
|
||||||
|
if !v.Migrated() {
|
||||||
|
// Migrate old settings into the vault.
|
||||||
|
if err := migrateOldSettings(v); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to migrate old settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate old accounts into the vault.
|
||||||
|
if err := migrateOldAccounts(locations, keychains, v); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to migrate old accounts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The vault has been migrated.
|
||||||
|
if err := v.SetMigrated(); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to mark vault as migrated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"lastVersion": v.GetLastVersion().String(),
|
||||||
|
"showAllMail": v.GetShowAllMail(),
|
||||||
|
"updateCh": v.GetUpdateChannel(),
|
||||||
|
"autoUpdate": v.GetAutoUpdate(),
|
||||||
|
"rollout": v.GetUpdateRollout(),
|
||||||
|
"DoH": v.GetProxyAllowed(),
|
||||||
|
}).Info("Vault loaded")
|
||||||
|
|
||||||
|
// Load the cookies from the vault.
|
||||||
|
return withCookieJar(v, func(cookieJar http.CookieJar) error {
|
||||||
|
// Create a new bridge instance.
|
||||||
|
return withBridge(c, exe, locations, version, identifier, crashHandler, reporter, v, cookieJar, keychains, func(b *bridge.Bridge, eventCh <-chan events.Event) error {
|
||||||
|
if insecure {
|
||||||
|
logrus.Warn("The vault key could not be retrieved; the vault will not be encrypted")
|
||||||
|
b.PushError(bridge.ErrVaultInsecure)
|
||||||
|
}
|
||||||
|
|
||||||
|
if corrupt {
|
||||||
|
logrus.Warn("The vault is corrupt and has been wiped")
|
||||||
|
b.PushError(bridge.ErrVaultCorrupt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old updates files
|
||||||
|
b.RemoveOldUpdates()
|
||||||
|
|
||||||
|
// Run the frontend.
|
||||||
|
return runFrontend(c, crashHandler, restarter, locations, b, eventCh, quitCh, c.Int(flagParentPID))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// if an error occurs, it must be logged now because we're about to close the log file.
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's another instance already running, try to raise it and exit.
|
||||||
|
func withSingleInstance(settingPath, lockFile string, version *semver.Version, fn func() error) error {
|
||||||
|
logrus.Debug("Checking for other instances")
|
||||||
|
defer logrus.Debug("Single instance stopped")
|
||||||
|
|
||||||
|
lock, err := checkSingleInstance(settingPath, lockFile, version)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Info("Another instance is already running; raising it")
|
||||||
|
|
||||||
|
if ok := focus.TryRaise(settingPath); !ok {
|
||||||
|
return fmt.Errorf("another instance is already running but it could not be raised")
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Info("The other instance has been raised")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := lock.Close(); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to close lock file")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize our logging system.
|
||||||
|
func withLogging(c *cli.Context, crashHandler *crash.Handler, locations *locations.Locations, fn func(closer io.Closer) error) error {
|
||||||
|
logrus.Debug("Initializing logging")
|
||||||
|
defer logrus.Debug("Logging stopped")
|
||||||
|
|
||||||
|
// Get a place to keep our logs.
|
||||||
|
logsPath, err := locations.ProvideLogsPath()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not provide logs path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithField("path", logsPath).Debug("Received logs path")
|
||||||
|
|
||||||
|
// Initialize logging.
|
||||||
|
sessionID := logging.NewSessionIDFromString(c.String(flagSessionID))
|
||||||
|
var closer io.Closer
|
||||||
|
if closer, err = logging.Init(
|
||||||
|
logsPath,
|
||||||
|
sessionID,
|
||||||
|
logging.BridgeShortAppName,
|
||||||
|
logging.DefaultMaxLogFileSize,
|
||||||
|
logging.DefaultPruningSize,
|
||||||
|
c.String(flagLogLevel),
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("could not initialize logging: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we dump a stack trace if we crash.
|
||||||
|
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath, sessionID, appShortName))
|
||||||
|
|
||||||
|
logrus.
|
||||||
|
WithField("appName", constants.FullAppName).
|
||||||
|
WithField("version", constants.Version).
|
||||||
|
WithField("revision", constants.Revision).
|
||||||
|
WithField("tag", constants.Tag).
|
||||||
|
WithField("build", constants.BuildTime).
|
||||||
|
WithField("runtime", runtime.GOOS).
|
||||||
|
WithField("args", os.Args).
|
||||||
|
WithField("SentryID", sentry.GetProtectedHostname()).
|
||||||
|
Info("Run app")
|
||||||
|
|
||||||
|
return fn(closer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLocations provides access to locations where we store our files.
|
||||||
|
func WithLocations(fn func(*locations.Locations) error) error {
|
||||||
|
logrus.Debug("Creating locations")
|
||||||
|
defer logrus.Debug("Locations stopped")
|
||||||
|
|
||||||
|
// Create a locations provider to determine where to store our files.
|
||||||
|
provider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, constants.ConfigName))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create locations provider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new locations object that will be used to provide paths to store files.
|
||||||
|
return fn(locations.New(provider, constants.ConfigName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start profiling if requested.
|
||||||
|
func withProfiler(c *cli.Context, fn func() error) error {
|
||||||
|
defer logrus.Debug("Profiler stopped")
|
||||||
|
|
||||||
|
if c.Bool(flagCPUProfile) {
|
||||||
|
logrus.Debug("Running with CPU profiling")
|
||||||
|
defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Bool(flagTraceProfile) {
|
||||||
|
logrus.Debug("Running with Trace profiling")
|
||||||
|
defer profile.Start(profile.TraceProfile, profile.ProfilePath(".")).Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Bool(flagMemProfile) {
|
||||||
|
logrus.Debug("Running with memory profiling")
|
||||||
|
defer profile.Start(profile.MemProfile, profile.MemProfileAllocs, profile.ProfilePath(".")).Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart the app if necessary.
|
||||||
|
func withRestarter(exe string, fn func(*restarter.Restarter) error) error {
|
||||||
|
logrus.Debug("Creating restarter")
|
||||||
|
defer logrus.Debug("Restarter stopped")
|
||||||
|
|
||||||
|
restarter := restarter.New(exe)
|
||||||
|
defer restarter.Restart()
|
||||||
|
|
||||||
|
return fn(restarter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle crashes if they occur.
|
||||||
|
func withCrashHandler(restarter *restarter.Restarter, reporter *sentry.Reporter, fn func(*crash.Handler, <-chan struct{}) error) error {
|
||||||
|
logrus.Debug("Creating crash handler")
|
||||||
|
defer logrus.Debug("Crash handler stopped")
|
||||||
|
|
||||||
|
crashHandler := crash.NewHandler(crash.ShowErrorNotification(constants.FullAppName))
|
||||||
|
defer async.HandlePanic(crashHandler)
|
||||||
|
|
||||||
|
// On crash, send crash report to Sentry.
|
||||||
|
crashHandler.AddRecoveryAction(reporter.ReportException)
|
||||||
|
|
||||||
|
// On crash, notify the user and restart the app.
|
||||||
|
crashHandler.AddRecoveryAction(crash.ShowErrorNotification(constants.FullAppName))
|
||||||
|
|
||||||
|
// On crash, restart the app.
|
||||||
|
crashHandler.AddRecoveryAction(func(any) error { restarter.Set(true, true); return nil })
|
||||||
|
|
||||||
|
// quitCh is closed when the app is quitting.
|
||||||
|
quitCh := make(chan struct{})
|
||||||
|
|
||||||
|
// On crash, quit the app.
|
||||||
|
crashHandler.AddRecoveryAction(func(any) error { close(quitCh); return nil })
|
||||||
|
|
||||||
|
return fn(crashHandler, quitCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a custom cookie jar to persist values across runs.
|
||||||
|
func withCookieJar(vault *vault.Vault, fn func(http.CookieJar) error) error {
|
||||||
|
logrus.Debug("Creating cookie jar")
|
||||||
|
defer logrus.Debug("Cookie jar stopped")
|
||||||
|
|
||||||
|
// Create the underlying cookie jar.
|
||||||
|
jar, err := cookiejar.New(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create cookie jar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the cookie jar which persists to the vault.
|
||||||
|
persister, err := cookies.NewCookieJar(jar, vault)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create cookie jar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setDeviceCookies(persister); err != nil {
|
||||||
|
return fmt.Errorf("could not set device cookies: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist the cookies to the vault when we close.
|
||||||
|
defer func() {
|
||||||
|
logrus.Debug("Persisting cookies")
|
||||||
|
|
||||||
|
if err := persister.PersistCookies(); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to persist cookies")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return fn(persister)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithKeychainList init the list of usable keychains.
|
||||||
|
func WithKeychainList(fn func(*keychain.List) error) error {
|
||||||
|
logrus.Debug("Creating keychain list")
|
||||||
|
defer logrus.Debug("Keychain list stop")
|
||||||
|
return fn(keychain.NewList())
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDeviceCookies(jar *cookies.Jar) error {
|
||||||
|
url, err := url.Parse(constants.APIHost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, value := range map[string]string{
|
||||||
|
"hhn": sentry.GetProtectedHostname(),
|
||||||
|
"tz": sentry.GetTimeZone(),
|
||||||
|
"lng": sentry.GetSystemLang(),
|
||||||
|
"clr": string(theme.DefaultTheme()),
|
||||||
|
} {
|
||||||
|
jar.SetCookies(url, []*http.Cookie{{Name: name, Value: value, Secure: true}})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -1,405 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Package base implements a common application base currently shared by bridge and IE.
|
|
||||||
// The base includes the following:
|
|
||||||
// - access to standard filesystem locations like config, cache, logging dirs
|
|
||||||
// - an extensible crash handler
|
|
||||||
// - versioned cache directory
|
|
||||||
// - persistent settings
|
|
||||||
// - event listener
|
|
||||||
// - credentials store
|
|
||||||
// - pmapi Manager
|
|
||||||
// In addition, the base initialises logging and reacts to command line arguments
|
|
||||||
// which control the log verbosity and enable cpu/memory profiling.
|
|
||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"runtime/pprof"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"github.com/ProtonMail/go-autostart"
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/api"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/cache"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/tls"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/cookies"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/crash"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/locations"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/logging"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/sentry"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/versioner"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
|
||||||
"github.com/allan-simon/go-singleinstance"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
flagCPUProfile = "cpu-prof"
|
|
||||||
flagCPUProfileShort = "p"
|
|
||||||
flagMemProfile = "mem-prof"
|
|
||||||
flagMemProfileShort = "m"
|
|
||||||
flagLogLevel = "log-level"
|
|
||||||
flagLogLevelShort = "l"
|
|
||||||
// FlagCLI indicate to start with command line interface.
|
|
||||||
FlagCLI = "cli"
|
|
||||||
flagCLIShort = "c"
|
|
||||||
flagRestart = "restart"
|
|
||||||
FlagLauncher = "launcher"
|
|
||||||
FlagNoWindow = "no-window"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Base struct {
|
|
||||||
SentryReporter *sentry.Reporter
|
|
||||||
CrashHandler *crash.Handler
|
|
||||||
Locations *locations.Locations
|
|
||||||
Settings *settings.Settings
|
|
||||||
Lock *os.File
|
|
||||||
Cache *cache.Cache
|
|
||||||
Listener listener.Listener
|
|
||||||
Creds *credentials.Store
|
|
||||||
CM pmapi.Manager
|
|
||||||
CookieJar *cookies.Jar
|
|
||||||
UserAgent *useragent.UserAgent
|
|
||||||
Updater *updater.Updater
|
|
||||||
Versioner *versioner.Versioner
|
|
||||||
TLS *tls.TLS
|
|
||||||
Autostart *autostart.App
|
|
||||||
|
|
||||||
Name string // the app's name
|
|
||||||
usage string // the app's usage description
|
|
||||||
command string // the command used to launch the app (either the exe path or the launcher path)
|
|
||||||
restart bool // whether the app is currently set to restart
|
|
||||||
|
|
||||||
teardown []func() error // actions to perform when app is exiting
|
|
||||||
}
|
|
||||||
|
|
||||||
func New( //nolint:funlen
|
|
||||||
appName,
|
|
||||||
appUsage,
|
|
||||||
configName,
|
|
||||||
updateURLName,
|
|
||||||
keychainName,
|
|
||||||
cacheVersion string,
|
|
||||||
) (*Base, error) {
|
|
||||||
userAgent := useragent.New()
|
|
||||||
|
|
||||||
sentryReporter := sentry.NewReporter(appName, constants.Version, userAgent)
|
|
||||||
|
|
||||||
crashHandler := crash.NewHandler(
|
|
||||||
sentryReporter.ReportException,
|
|
||||||
crash.ShowErrorNotification(appName),
|
|
||||||
)
|
|
||||||
defer crashHandler.HandlePanic()
|
|
||||||
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
os.Args = StripProcessSerialNumber(os.Args)
|
|
||||||
|
|
||||||
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
locations := locations.New(locationsProvider, configName)
|
|
||||||
|
|
||||||
logsPath, err := locations.ProvideLogsPath()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := logging.Init(logsPath); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
|
|
||||||
|
|
||||||
if err := migrateFiles(configName); err != nil {
|
|
||||||
logrus.WithError(err).Warn("Old config files could not be migrated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := locations.Clean(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsPath, err := locations.ProvideSettingsPath()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
settingsObj := settings.New(settingsPath)
|
|
||||||
|
|
||||||
lock, err := singleinstance.CreateLockFile(locations.GetLockFile())
|
|
||||||
if err != nil {
|
|
||||||
logrus.Warnf("%v is already running", appName)
|
|
||||||
return nil, api.CheckOtherInstanceAndFocus(settingsObj.GetInt(settings.APIPortKey))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := migrateRebranding(settingsObj, keychainName); err != nil {
|
|
||||||
logrus.WithError(err).Warn("Rebranding migration failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
cachePath, err := locations.ProvideCachePath()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cache, err := cache.New(cachePath, cacheVersion)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := cache.RemoveOldVersions(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
listener := listener.New()
|
|
||||||
events.SetupEvents(listener)
|
|
||||||
|
|
||||||
// If we can't load the keychain for whatever reason,
|
|
||||||
// we signal to frontend and supply a dummy keychain that always returns errors.
|
|
||||||
kc, err := keychain.NewKeychain(settingsObj, keychainName)
|
|
||||||
if err != nil {
|
|
||||||
listener.Emit(events.CredentialsErrorEvent, err.Error())
|
|
||||||
kc = keychain.NewMissingKeychain()
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := pmapi.NewConfig(configName, constants.Version)
|
|
||||||
cfg.GetUserAgent = userAgent.String
|
|
||||||
cfg.UpgradeApplicationHandler = func() { listener.Emit(events.UpgradeApplicationEvent, "") }
|
|
||||||
cfg.TLSIssueHandler = func() { listener.Emit(events.TLSCertIssue, "") }
|
|
||||||
|
|
||||||
cm := pmapi.New(cfg)
|
|
||||||
|
|
||||||
sentryReporter.SetClientFromManager(cm)
|
|
||||||
|
|
||||||
cm.AddConnectionObserver(pmapi.NewConnectionObserver(
|
|
||||||
func() { listener.Emit(events.InternetConnChangedEvent, events.InternetOff) },
|
|
||||||
func() { listener.Emit(events.InternetConnChangedEvent, events.InternetOn) },
|
|
||||||
))
|
|
||||||
|
|
||||||
jar, err := cookies.NewCookieJar(settingsObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cm.SetCookieJar(jar)
|
|
||||||
|
|
||||||
key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
kr, err := crypto.NewKeyRing(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
updatesDir, err := locations.ProvideUpdatesPath()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
versioner := versioner.New(updatesDir)
|
|
||||||
installer := updater.NewInstaller(versioner)
|
|
||||||
updater := updater.New(
|
|
||||||
cm,
|
|
||||||
installer,
|
|
||||||
settingsObj,
|
|
||||||
kr,
|
|
||||||
semver.MustParse(constants.Version),
|
|
||||||
updateURLName,
|
|
||||||
runtime.GOOS,
|
|
||||||
)
|
|
||||||
|
|
||||||
exe, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
autostart := &autostart.App{
|
|
||||||
Name: startupNameForRebranding(appName),
|
|
||||||
DisplayName: appName,
|
|
||||||
Exec: []string{exe, "--" + FlagNoWindow},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Base{
|
|
||||||
SentryReporter: sentryReporter,
|
|
||||||
CrashHandler: crashHandler,
|
|
||||||
Locations: locations,
|
|
||||||
Settings: settingsObj,
|
|
||||||
Lock: lock,
|
|
||||||
Cache: cache,
|
|
||||||
Listener: listener,
|
|
||||||
Creds: credentials.NewStore(kc),
|
|
||||||
CM: cm,
|
|
||||||
CookieJar: jar,
|
|
||||||
UserAgent: userAgent,
|
|
||||||
Updater: updater,
|
|
||||||
Versioner: versioner,
|
|
||||||
TLS: tls.New(settingsPath),
|
|
||||||
Autostart: autostart,
|
|
||||||
|
|
||||||
Name: appName,
|
|
||||||
usage: appUsage,
|
|
||||||
|
|
||||||
// By default, the command is the app's executable.
|
|
||||||
// This can be changed at runtime by using the "--launcher" flag.
|
|
||||||
command: exe,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Base) NewApp(mainLoop func(*Base, *cli.Context) error) *cli.App {
|
|
||||||
app := cli.NewApp()
|
|
||||||
|
|
||||||
app.Name = b.Name
|
|
||||||
app.Usage = b.usage
|
|
||||||
app.Version = constants.Version
|
|
||||||
app.Action = b.wrapMainLoop(mainLoop)
|
|
||||||
app.Flags = []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: flagCPUProfile,
|
|
||||||
Aliases: []string{flagCPUProfileShort},
|
|
||||||
Usage: "Generate CPU profile",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: flagMemProfile,
|
|
||||||
Aliases: []string{flagMemProfileShort},
|
|
||||||
Usage: "Generate memory profile",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: flagLogLevel,
|
|
||||||
Aliases: []string{flagLogLevelShort},
|
|
||||||
Usage: "Set the log level (one of panic, fatal, error, warn, info, debug)",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: FlagCLI,
|
|
||||||
Aliases: []string{flagCLIShort},
|
|
||||||
Usage: "Use command line interface",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: FlagNoWindow,
|
|
||||||
Usage: "Don't show window after start",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: flagRestart,
|
|
||||||
Usage: "The number of times the application has already restarted",
|
|
||||||
Hidden: true,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: FlagLauncher,
|
|
||||||
Usage: "The launcher to use to restart the application",
|
|
||||||
Hidden: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetToRestart sets the app to restart the next time it is closed.
|
|
||||||
func (b *Base) SetToRestart() {
|
|
||||||
b.restart = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTeardownAction adds an action to perform during app teardown.
|
|
||||||
func (b *Base) AddTeardownAction(fn func() error) {
|
|
||||||
b.teardown = append(b.teardown, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Base) wrapMainLoop(appMainLoop func(*Base, *cli.Context) error) cli.ActionFunc { //nolint:funlen
|
|
||||||
return func(c *cli.Context) error {
|
|
||||||
defer b.CrashHandler.HandlePanic()
|
|
||||||
defer func() { _ = b.Lock.Close() }()
|
|
||||||
|
|
||||||
// If launcher was used to start the app, use that for restart
|
|
||||||
// and autostart.
|
|
||||||
if launcher := c.String(FlagLauncher); launcher != "" {
|
|
||||||
b.command = launcher
|
|
||||||
// Bridge supports no-window option which we should use
|
|
||||||
// for autostart.
|
|
||||||
b.Autostart.Exec = []string{launcher, "--" + FlagNoWindow}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Bool(flagCPUProfile) {
|
|
||||||
startCPUProfile()
|
|
||||||
defer pprof.StopCPUProfile()
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Bool(flagMemProfile) {
|
|
||||||
defer makeMemoryProfile()
|
|
||||||
}
|
|
||||||
|
|
||||||
logging.SetLevel(c.String(flagLogLevel))
|
|
||||||
b.CM.SetLogging(logrus.WithField("pkg", "pmapi"), logrus.GetLevel() == logrus.TraceLevel)
|
|
||||||
|
|
||||||
logrus.
|
|
||||||
WithField("appName", b.Name).
|
|
||||||
WithField("version", constants.Version).
|
|
||||||
WithField("revision", constants.Revision).
|
|
||||||
WithField("build", constants.BuildTime).
|
|
||||||
WithField("runtime", runtime.GOOS).
|
|
||||||
WithField("args", os.Args).
|
|
||||||
Info("Run app")
|
|
||||||
|
|
||||||
b.CrashHandler.AddRecoveryAction(func(interface{}) error {
|
|
||||||
sentry.Flush(2 * time.Second)
|
|
||||||
|
|
||||||
if c.Int(flagRestart) > maxAllowedRestarts {
|
|
||||||
logrus.
|
|
||||||
WithField("restart", c.Int("restart")).
|
|
||||||
Warn("Not restarting, already restarted too many times")
|
|
||||||
os.Exit(1)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.restartApp(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := appMainLoop(b, c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.doTeardown(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.restart {
|
|
||||||
return b.restartApp(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Base) doTeardown() error {
|
|
||||||
for _, action := range b.teardown {
|
|
||||||
if err := action(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/locations"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// migrateFiles migrates files from their old (pre-refactor) locations to their new locations.
|
|
||||||
// We can remove this eventually.
|
|
||||||
//
|
|
||||||
// | entity | old location | new location |
|
|
||||||
// |-----------|-------------------------------------------|----------------------------------------|
|
|
||||||
// | prefs | ~/.cache/protonmail/<app>/c11/prefs.json | ~/.config/protonmail/<app>/prefs.json |
|
|
||||||
// | c11 1.5.x | ~/.cache/protonmail/<app>/c11 | ~/.cache/protonmail/<app>/cache/c11 |
|
|
||||||
// | c11 1.6.x | ~/.cache/protonmail/<app>/cache/c11 | ~/.config/protonmail/<app>/cache/c11 |
|
|
||||||
// | updates | ~/.cache/protonmail/<app>/updates | ~/.config/protonmail/<app>/updates |.
|
|
||||||
func migrateFiles(configName string) error {
|
|
||||||
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
locations := locations.New(locationsProvider, configName)
|
|
||||||
userCacheDir := locationsProvider.UserCache()
|
|
||||||
|
|
||||||
if err := migratePrefsFrom15x(locations, userCacheDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := migrateCacheFromBoth15xAnd16x(locations, userCacheDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := migrateUpdatesFrom16x(configName, locations); err != nil { //nolint:revive It is more clear to structure this way
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func migratePrefsFrom15x(locations *locations.Locations, userCacheDir string) error {
|
|
||||||
newSettingsDir, err := locations.ProvideSettingsPath()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return moveIfExists(
|
|
||||||
filepath.Join(userCacheDir, "c11", "prefs.json"),
|
|
||||||
filepath.Join(newSettingsDir, "prefs.json"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateCacheFromBoth15xAnd16x(locations *locations.Locations, userCacheDir string) error {
|
|
||||||
olderCacheDir := userCacheDir
|
|
||||||
newerCacheDir := locations.GetOldCachePath()
|
|
||||||
latestCacheDir, err := locations.ProvideCachePath()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migration for versions before 1.6.x.
|
|
||||||
if err := moveIfExists(
|
|
||||||
filepath.Join(olderCacheDir, "c11"),
|
|
||||||
filepath.Join(latestCacheDir, "c11"),
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migration for versions 1.6.x.
|
|
||||||
return moveIfExists(
|
|
||||||
filepath.Join(newerCacheDir, "c11"),
|
|
||||||
filepath.Join(latestCacheDir, "c11"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateUpdatesFrom16x(configName string, locations *locations.Locations) error {
|
|
||||||
// In order to properly update Bridge 1.6.X and higher we need to
|
|
||||||
// change the launcher first. Since this is not part of automatic
|
|
||||||
// updates the migration must wait until manual update. Until that
|
|
||||||
// we need to keep old path.
|
|
||||||
if configName == "bridge" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
oldUpdatesPath := locations.GetOldUpdatesPath()
|
|
||||||
// Do not use ProvideUpdatesPath, that creates dir right away.
|
|
||||||
newUpdatesPath := locations.GetUpdatesPath()
|
|
||||||
|
|
||||||
return moveIfExists(oldUpdatesPath, newUpdatesPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func moveIfExists(source, destination string) error {
|
|
||||||
l := logrus.WithField("source", source).WithField("destination", destination)
|
|
||||||
|
|
||||||
if _, err := os.Stat(source); os.IsNotExist(err) {
|
|
||||||
l.Info("No need to migrate file, source doesn't exist")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(destination); !os.IsNotExist(err) {
|
|
||||||
// Once migrated, files should not stay in source anymore. Therefore
|
|
||||||
// if some files are still in source location but target already exist,
|
|
||||||
// it's suspicious. Could happen by installing new version, then the
|
|
||||||
// old one because of some reason, and then the new one again.
|
|
||||||
// Good to see as warning because it could be a reason why Bridge is
|
|
||||||
// behaving weirdly, like wrong configuration, or db re-sync and so on.
|
|
||||||
l.Warn("No need to migrate file, target already exists")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Info("Migrating files")
|
|
||||||
return os.Rename(source, destination)
|
|
||||||
}
|
|
||||||
@ -1,197 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
|
||||||
"github.com/hashicorp/go-multierror"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const darwin = "darwin"
|
|
||||||
|
|
||||||
func migrateRebranding(settingsObj *settings.Settings, keychainName string) (result error) {
|
|
||||||
if err := migrateStartupBeforeRebranding(); err != nil {
|
|
||||||
result = multierror.Append(result, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lastUsedVersion := settingsObj.Get(settings.LastVersionKey)
|
|
||||||
|
|
||||||
// Skipping migration: it is first bridge start or cache was cleared.
|
|
||||||
if lastUsedVersion == "" {
|
|
||||||
settingsObj.SetBool(settings.RebrandingMigrationKey, true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skipping rest of migration: already done
|
|
||||||
if settingsObj.GetBool(settings.RebrandingMigrationKey) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "windows", "linux":
|
|
||||||
// GODT-1260 we would need admin rights to changes desktop files
|
|
||||||
// and start menu items.
|
|
||||||
settingsObj.SetBool(settings.RebrandingMigrationKey, true)
|
|
||||||
case darwin:
|
|
||||||
if shouldContinue, err := isMacBeforeRebranding(); !shouldContinue || err != nil {
|
|
||||||
if err != nil {
|
|
||||||
result = multierror.Append(result, err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := migrateMacKeychainBeforeRebranding(settingsObj, keychainName); err != nil {
|
|
||||||
result = multierror.Append(result, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsObj.SetBool(settings.RebrandingMigrationKey, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// migrateMacKeychainBeforeRebranding deals with write access restriction to
|
|
||||||
// mac keychain passwords which are caused by application renaming. The old
|
|
||||||
// passwords are copied under new name in order to have write access afer
|
|
||||||
// renaming.
|
|
||||||
func migrateMacKeychainBeforeRebranding(settingsObj *settings.Settings, keychainName string) error {
|
|
||||||
l := logrus.WithField("pkg", "app/base/migration")
|
|
||||||
l.Warn("Migrating mac keychain")
|
|
||||||
|
|
||||||
helperConstructor, ok := keychain.Helpers["macos-keychain"]
|
|
||||||
if !ok {
|
|
||||||
return errors.New("cannot find macos-keychain helper")
|
|
||||||
}
|
|
||||||
|
|
||||||
oldKC, err := helperConstructor("ProtonMailBridgeService")
|
|
||||||
if err != nil {
|
|
||||||
l.WithError(err).Error("Keychain constructor failed")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
idByURL, err := oldKC.List()
|
|
||||||
if err != nil {
|
|
||||||
l.WithError(err).Error("List old keychain failed")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newKC, err := keychain.NewKeychain(settingsObj, keychainName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for url, id := range idByURL {
|
|
||||||
li := l.WithField("id", id).WithField("url", url)
|
|
||||||
userID, secret, err := oldKC.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
li.WithField("userID", userID).
|
|
||||||
WithField("err", err).
|
|
||||||
Error("Faild to get old item")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, _, err := newKC.Get(userID); err == nil {
|
|
||||||
li.Warn("Skipping migration, item already exists.")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := newKC.Put(userID, secret); err != nil {
|
|
||||||
li.WithError(err).Error("Failed to migrate user")
|
|
||||||
}
|
|
||||||
|
|
||||||
li.Info("Item migrated")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// migrateStartupBeforeRebranding removes old startup links. The creation of new links is
|
|
||||||
// handled by bridge initialisation.
|
|
||||||
func migrateStartupBeforeRebranding() error {
|
|
||||||
path, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "windows":
|
|
||||||
path = filepath.Join(path, `AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\ProtonMail Bridge.lnk`)
|
|
||||||
case "linux":
|
|
||||||
path = filepath.Join(path, `.config/autostart/ProtonMail Bridge.desktop`)
|
|
||||||
case darwin:
|
|
||||||
path = filepath.Join(path, `Library/LaunchAgents/ProtonMail Bridge.plist`)
|
|
||||||
default:
|
|
||||||
return errors.New("unknown GOOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.WithField("pkg", "app/base/migration").Warn("Migrating autostartup links")
|
|
||||||
return os.Remove(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// startupNameForRebranding returns the name for autostart launcher based on
|
|
||||||
// type of rebranded instance i.e. update or manual.
|
|
||||||
//
|
|
||||||
// This only affects darwin when udpate re-writes the old startup and then
|
|
||||||
// manual installed it would not run proper exe. Therefore we return "old" name
|
|
||||||
// for updates and "new" name for manual which would be properly migrated.
|
|
||||||
//
|
|
||||||
// For orther (linux and windows) the link is always pointing to launcher which
|
|
||||||
// path didn't changed.
|
|
||||||
func startupNameForRebranding(origin string) string {
|
|
||||||
if runtime.GOOS == darwin {
|
|
||||||
if path, err := os.Executable(); err == nil && strings.Contains(path, "ProtonMail Bridge") {
|
|
||||||
return "ProtonMail Bridge"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No need to solve for other OS. See comment above.
|
|
||||||
return origin
|
|
||||||
}
|
|
||||||
|
|
||||||
// isBeforeRebranding decide if last used version was older than 2.2.0. If
|
|
||||||
// cannot decide it returns false with error.
|
|
||||||
func isMacBeforeRebranding() (bool, error) {
|
|
||||||
// previous version | update | do mac migration |
|
|
||||||
// | first | false |
|
|
||||||
// cleared-cache | manual | false |
|
|
||||||
// cleared-cache | in-app | false |
|
|
||||||
// old | in-app | false |
|
|
||||||
// old in-app | in-app | false |
|
|
||||||
// old | manual | true |
|
|
||||||
// old in-app | manual | true |
|
|
||||||
// manual | in-app | false |
|
|
||||||
|
|
||||||
// Skip if it was in-app update and not manual
|
|
||||||
if path, err := os.Executable(); err != nil || strings.Contains(path, "ProtonMail Bridge") {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"runtime/pprof"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// startCPUProfile starts CPU pprof.
|
|
||||||
func startCPUProfile() {
|
|
||||||
f, err := os.Create("./cpu.pprof")
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal("Could not create CPU profile: ", err)
|
|
||||||
}
|
|
||||||
if err := pprof.StartCPUProfile(f); err != nil {
|
|
||||||
logrus.Fatal("Could not start CPU profile: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeMemoryProfile generates memory pprof.
|
|
||||||
func makeMemoryProfile() {
|
|
||||||
name := "./mem.pprof"
|
|
||||||
f, err := os.Create(name)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal("Could not create memory profile: ", err)
|
|
||||||
}
|
|
||||||
if abs, err := filepath.Abs(name); err == nil {
|
|
||||||
name = abs
|
|
||||||
}
|
|
||||||
logrus.Info("Writing memory profile to ", name)
|
|
||||||
runtime.GC() // get up-to-date statistics
|
|
||||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
|
||||||
logrus.Fatal("Could not write memory profile: ", err)
|
|
||||||
}
|
|
||||||
_ = f.Close()
|
|
||||||
}
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// maxAllowedRestarts controls after how many crashes the app will give up restarting.
|
|
||||||
const maxAllowedRestarts = 10
|
|
||||||
|
|
||||||
func (b *Base) restartApp(crash bool) error {
|
|
||||||
var args []string
|
|
||||||
|
|
||||||
if crash {
|
|
||||||
args = incrementRestartFlag(os.Args)[1:]
|
|
||||||
defer func() { os.Exit(1) }()
|
|
||||||
} else {
|
|
||||||
args = os.Args[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.
|
|
||||||
WithField("command", b.command).
|
|
||||||
WithField("args", args).
|
|
||||||
Warn("Restarting")
|
|
||||||
|
|
||||||
return exec.Command(b.command, args...).Start() //nolint:gosec
|
|
||||||
}
|
|
||||||
|
|
||||||
// incrementRestartFlag increments the value of the restart flag.
|
|
||||||
// If no such flag is present, it is added with initial value 1.
|
|
||||||
func incrementRestartFlag(args []string) []string {
|
|
||||||
res := append([]string{}, args...)
|
|
||||||
|
|
||||||
hasFlag := false
|
|
||||||
|
|
||||||
for k, v := range res {
|
|
||||||
if v != "--restart" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
hasFlag = true
|
|
||||||
|
|
||||||
if k+1 >= len(res) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := strconv.Atoi(res[k+1])
|
|
||||||
if err != nil {
|
|
||||||
res[k+1] = "1"
|
|
||||||
} else {
|
|
||||||
res[k+1] = strconv.Itoa(n + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasFlag {
|
|
||||||
res = append(res, "--restart", "1")
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIncrementRestartFlag(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
in []string
|
|
||||||
out []string
|
|
||||||
}{
|
|
||||||
{[]string{"./bridge", "--restart", "1"}, []string{"./bridge", "--restart", "2"}},
|
|
||||||
{[]string{"./bridge", "--restart", "2"}, []string{"./bridge", "--restart", "3"}},
|
|
||||||
{[]string{"./bridge", "--other", "--restart", "2"}, []string{"./bridge", "--other", "--restart", "3"}},
|
|
||||||
{[]string{"./bridge", "--restart", "2", "--other"}, []string{"./bridge", "--restart", "3", "--other"}},
|
|
||||||
{[]string{"./bridge", "--restart", "2", "--other", "2"}, []string{"./bridge", "--restart", "3", "--other", "2"}},
|
|
||||||
{[]string{"./bridge"}, []string{"./bridge", "--restart", "1"}},
|
|
||||||
{[]string{"./bridge", "--something"}, []string{"./bridge", "--something", "--restart", "1"}},
|
|
||||||
{[]string{"./bridge", "--something", "--else"}, []string{"./bridge", "--something", "--else", "--restart", "1"}},
|
|
||||||
{[]string{"./bridge", "--restart", "bad"}, []string{"./bridge", "--restart", "1"}},
|
|
||||||
{[]string{"./bridge", "--restart", "bad", "--other"}, []string{"./bridge", "--restart", "1", "--other"}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(strings.Join(tt.in, " "), func(t *testing.T) {
|
|
||||||
assert.Equal(t, tt.out, incrementRestartFlag(tt.in))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
167
internal/app/bridge.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/gluon/imap"
|
||||||
|
"github.com/ProtonMail/go-autostart"
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/crash"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/dialer"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/versioner"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deleteOldGoIMAPFiles Set with `-ldflags -X app.deleteOldGoIMAPFiles=true` to enable cleanup of old imap cache data.
|
||||||
|
var deleteOldGoIMAPFiles bool //nolint:gochecknoglobals
|
||||||
|
|
||||||
|
// withBridge creates and tears down the bridge.
|
||||||
|
func withBridge(
|
||||||
|
c *cli.Context,
|
||||||
|
exe string,
|
||||||
|
locations *locations.Locations,
|
||||||
|
version *semver.Version,
|
||||||
|
identifier *useragent.UserAgent,
|
||||||
|
crashHandler *crash.Handler,
|
||||||
|
reporter *sentry.Reporter,
|
||||||
|
vault *vault.Vault,
|
||||||
|
cookieJar http.CookieJar,
|
||||||
|
keychains *keychain.List,
|
||||||
|
fn func(*bridge.Bridge, <-chan events.Event) error,
|
||||||
|
) error {
|
||||||
|
logrus.Debug("Creating bridge")
|
||||||
|
defer logrus.Debug("Bridge stopped")
|
||||||
|
|
||||||
|
// Delete old go-imap cache files
|
||||||
|
if deleteOldGoIMAPFiles {
|
||||||
|
logrus.Debug("Deleting old go-imap cache files")
|
||||||
|
|
||||||
|
if err := locations.CleanGoIMAPCache(); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to remove old go-imap cache")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the underlying dialer used by the bridge.
|
||||||
|
// It only connects to trusted servers and reports any untrusted servers it finds.
|
||||||
|
pinningDialer := dialer.NewPinningTLSDialer(
|
||||||
|
dialer.NewBasicTLSDialer(constants.APIHost),
|
||||||
|
dialer.NewTLSReporter(constants.APIHost, constants.AppVersion(version.Original()), identifier, dialer.TrustedAPIPins),
|
||||||
|
dialer.NewTLSPinChecker(dialer.TrustedAPIPins),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a proxy dialer which switches to a proxy if the request fails.
|
||||||
|
proxyDialer := dialer.NewProxyTLSDialer(pinningDialer, constants.APIHost, crashHandler)
|
||||||
|
|
||||||
|
// Create the autostarter.
|
||||||
|
autostarter := newAutostarter(exe)
|
||||||
|
|
||||||
|
// Create the update installer.
|
||||||
|
updater, err := newUpdater(locations)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create updater: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new bridge.
|
||||||
|
bridge, eventCh, err := bridge.New(
|
||||||
|
// The app stuff.
|
||||||
|
locations,
|
||||||
|
vault,
|
||||||
|
autostarter,
|
||||||
|
updater,
|
||||||
|
version,
|
||||||
|
keychains,
|
||||||
|
|
||||||
|
// The API stuff.
|
||||||
|
constants.APIHost,
|
||||||
|
cookieJar,
|
||||||
|
identifier,
|
||||||
|
pinningDialer,
|
||||||
|
dialer.CreateTransportWithDialer(proxyDialer),
|
||||||
|
proxyDialer,
|
||||||
|
|
||||||
|
// Crash and report stuff
|
||||||
|
crashHandler,
|
||||||
|
reporter,
|
||||||
|
imap.DefaultEpochUIDValidityGenerator(),
|
||||||
|
nil,
|
||||||
|
|
||||||
|
// The logging stuff.
|
||||||
|
c.String(flagLogIMAP) == "client" || c.String(flagLogIMAP) == "all",
|
||||||
|
c.String(flagLogIMAP) == "server" || c.String(flagLogIMAP) == "all",
|
||||||
|
c.Bool(flagLogSMTP),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create bridge: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we close bridge when we exit.
|
||||||
|
defer bridge.Close(c.Context)
|
||||||
|
|
||||||
|
return fn(bridge, eventCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAutostarter(exe string) *autostart.App {
|
||||||
|
logrus.Debug("Creating autostarter")
|
||||||
|
|
||||||
|
return &autostart.App{
|
||||||
|
Name: constants.FullAppName,
|
||||||
|
DisplayName: constants.FullAppName,
|
||||||
|
Exec: []string{exe, "--" + flagNoWindow},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUpdater(locations *locations.Locations) (*updater.Updater, error) {
|
||||||
|
updatesDir, err := locations.ProvideUpdatesPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not provide updates path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithField("updates", updatesDir).Debug("Creating updater")
|
||||||
|
|
||||||
|
key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create key from armored: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
verifier, err := crypto.NewKeyRing(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create key ring: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return updater.NewUpdater(
|
||||||
|
versioner.New(updatesDir),
|
||||||
|
verifier,
|
||||||
|
constants.UpdateName,
|
||||||
|
runtime.GOOS,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
@ -1,314 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Package bridge implements the bridge CLI application.
|
|
||||||
package bridge
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/api"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/app/base"
|
|
||||||
pkgBridge "github.com/ProtonMail/proton-bridge/internal/bridge"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
|
||||||
pkgTLS "github.com/ProtonMail/proton-bridge/internal/config/tls"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/imap"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/smtp"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/store/cache"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/message"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
flagLogIMAP = "log-imap"
|
|
||||||
flagLogSMTP = "log-smtp"
|
|
||||||
flagNonInteractive = "noninteractive"
|
|
||||||
|
|
||||||
// Memory cache was estimated by empirical usage in past and it was set to 100MB.
|
|
||||||
// NOTE: This value must not be less than maximal size of one email (~30MB).
|
|
||||||
inMemoryCacheLimnit = 100 * (1 << 20)
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(base *base.Base) *cli.App {
|
|
||||||
app := base.NewApp(mailLoop)
|
|
||||||
|
|
||||||
app.Flags = append(app.Flags, []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: flagLogIMAP,
|
|
||||||
Usage: "Enable logging of IMAP communications (all|client|server) (may contain decrypted data!)"},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: flagLogSMTP,
|
|
||||||
Usage: "Enable logging of SMTP communications (may contain decrypted data!)"},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: flagNonInteractive,
|
|
||||||
Usage: "Start Bridge entirely noninteractively"},
|
|
||||||
}...)
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
func mailLoop(b *base.Base, c *cli.Context) error { //nolint:funlen
|
|
||||||
tlsConfig, err := loadTLSConfig(b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GODT-1481: Always turn off reporting of unencrypted recipient in v2.
|
|
||||||
b.Settings.SetBool(settings.ReportOutgoingNoEncKey, false)
|
|
||||||
|
|
||||||
cache, cacheErr := loadMessageCache(b)
|
|
||||||
if cacheErr != nil {
|
|
||||||
logrus.WithError(cacheErr).Error("Could not load local cache.")
|
|
||||||
}
|
|
||||||
|
|
||||||
builder := message.NewBuilder(
|
|
||||||
b.Settings.GetInt(settings.FetchWorkers),
|
|
||||||
b.Settings.GetInt(settings.AttachmentWorkers),
|
|
||||||
)
|
|
||||||
|
|
||||||
bridge := pkgBridge.New(
|
|
||||||
b.Locations,
|
|
||||||
b.Cache,
|
|
||||||
b.Settings,
|
|
||||||
b.SentryReporter,
|
|
||||||
b.CrashHandler,
|
|
||||||
b.Listener,
|
|
||||||
cache,
|
|
||||||
builder,
|
|
||||||
b.CM,
|
|
||||||
b.Creds,
|
|
||||||
b.Updater,
|
|
||||||
b.Versioner,
|
|
||||||
b.Autostart,
|
|
||||||
)
|
|
||||||
imapBackend := imap.NewIMAPBackend(b.CrashHandler, b.Listener, b.Cache, b.Settings, bridge)
|
|
||||||
smtpBackend := smtp.NewSMTPBackend(b.CrashHandler, b.Listener, b.Settings, bridge)
|
|
||||||
|
|
||||||
if cacheErr != nil {
|
|
||||||
bridge.AddError(pkgBridge.ErrLocalCacheUnavailable)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer b.CrashHandler.HandlePanic()
|
|
||||||
api.NewAPIServer(b.Settings, b.Listener).ListenAndServe()
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer b.CrashHandler.HandlePanic()
|
|
||||||
imapPort := b.Settings.GetInt(settings.IMAPPortKey)
|
|
||||||
imap.NewIMAPServer(
|
|
||||||
b.CrashHandler,
|
|
||||||
c.String(flagLogIMAP) == "client" || c.String(flagLogIMAP) == "all",
|
|
||||||
c.String(flagLogIMAP) == "server" || c.String(flagLogIMAP) == "all",
|
|
||||||
imapPort, tlsConfig, imapBackend, b.UserAgent, b.Listener).ListenAndServe()
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer b.CrashHandler.HandlePanic()
|
|
||||||
smtpPort := b.Settings.GetInt(settings.SMTPPortKey)
|
|
||||||
useSSL := b.Settings.GetBool(settings.SMTPSSLKey)
|
|
||||||
smtp.NewSMTPServer(
|
|
||||||
b.CrashHandler,
|
|
||||||
c.Bool(flagLogSMTP),
|
|
||||||
smtpPort, useSSL, tlsConfig, smtpBackend, b.Listener).ListenAndServe()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// We want to remove old versions if the app exits successfully.
|
|
||||||
b.AddTeardownAction(b.Versioner.RemoveOldVersions)
|
|
||||||
|
|
||||||
// We want cookies to be saved to disk so they are loaded the next time.
|
|
||||||
b.AddTeardownAction(b.CookieJar.PersistCookies)
|
|
||||||
|
|
||||||
var frontendMode string
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case c.Bool(base.FlagCLI):
|
|
||||||
frontendMode = "cli"
|
|
||||||
case c.Bool(flagNonInteractive):
|
|
||||||
return <-(make(chan error)) // Block forever.
|
|
||||||
default:
|
|
||||||
frontendMode = "qt"
|
|
||||||
}
|
|
||||||
|
|
||||||
f := frontend.New(
|
|
||||||
constants.Version,
|
|
||||||
constants.BuildVersion,
|
|
||||||
b.Name,
|
|
||||||
frontendMode,
|
|
||||||
!c.Bool(base.FlagNoWindow),
|
|
||||||
b.CrashHandler,
|
|
||||||
b.Locations,
|
|
||||||
b.Settings,
|
|
||||||
b.Listener,
|
|
||||||
b.Updater,
|
|
||||||
b.UserAgent,
|
|
||||||
bridge,
|
|
||||||
smtpBackend,
|
|
||||||
b,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Watch for updates routine
|
|
||||||
go func() {
|
|
||||||
ticker := time.NewTicker(constants.UpdateCheckInterval)
|
|
||||||
|
|
||||||
for {
|
|
||||||
checkAndHandleUpdate(b.Updater, f, b.Settings.GetBool(settings.AutoUpdateKey))
|
|
||||||
<-ticker.C
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return f.Loop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadTLSConfig(b *base.Base) (*tls.Config, error) {
|
|
||||||
if !b.TLS.HasCerts() {
|
|
||||||
if err := generateTLSCerts(b); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig, err := b.TLS.GetConfig()
|
|
||||||
if err == nil {
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.WithError(err).Error("Failed to load TLS config, regenerating certificates")
|
|
||||||
|
|
||||||
if err := generateTLSCerts(b); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.TLS.GetConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTLSCerts(b *base.Base) error {
|
|
||||||
template, err := pkgTLS.NewTLSTemplate()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to generate TLS template")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.TLS.GenerateCerts(template); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to generate TLS certs")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.TLS.InstallCerts(); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to install TLS certs")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool) {
|
|
||||||
log := logrus.WithField("pkg", "app/bridge")
|
|
||||||
version, err := u.Check()
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("An error occurred while checking for updates")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.WaitUntilFrontendIsReady()
|
|
||||||
|
|
||||||
// Update links in UI
|
|
||||||
f.SetVersion(version)
|
|
||||||
|
|
||||||
if !u.IsUpdateApplicable(version) {
|
|
||||||
log.Info("No need to update")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithField("version", version.Version).Info("An update is available")
|
|
||||||
|
|
||||||
if !autoUpdate {
|
|
||||||
f.NotifyManualUpdate(version, u.CanInstall(version))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !u.CanInstall(version) {
|
|
||||||
log.Info("A manual update is required")
|
|
||||||
f.NotifySilentUpdateError(updater.ErrManualUpdateRequired)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := u.InstallUpdate(version); err != nil {
|
|
||||||
if errors.Cause(err) == updater.ErrDownloadVerify {
|
|
||||||
log.WithError(err).Warning("Skipping update installation due to temporary error")
|
|
||||||
} else {
|
|
||||||
log.WithError(err).Error("The update couldn't be installed")
|
|
||||||
f.NotifySilentUpdateError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.NotifySilentUpdateInstalled()
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadMessageCache loads local cache in case it is enabled in settings and available.
|
|
||||||
// In any other case it is returning in-memory cache. Could also return an error in case
|
|
||||||
// local cache is enabled but unavailable (in-memory cache will be returned nevertheless).
|
|
||||||
func loadMessageCache(b *base.Base) (cache.Cache, error) {
|
|
||||||
if !b.Settings.GetBool(settings.CacheEnabledKey) {
|
|
||||||
return cache.NewInMemoryCache(inMemoryCacheLimnit), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var compressor cache.Compressor
|
|
||||||
|
|
||||||
// NOTE(GODT-1158): Changing compression is not an option currently
|
|
||||||
// available for user but, if user changes compression setting we have
|
|
||||||
// to nuke the cache.
|
|
||||||
if b.Settings.GetBool(settings.CacheCompressionKey) {
|
|
||||||
compressor = &cache.GZipCompressor{}
|
|
||||||
} else {
|
|
||||||
compressor = &cache.NoopCompressor{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var path string
|
|
||||||
|
|
||||||
if customPath := b.Settings.Get(settings.CacheLocationKey); customPath != "" {
|
|
||||||
path = customPath
|
|
||||||
} else {
|
|
||||||
path = b.Cache.GetDefaultMessageCacheDir()
|
|
||||||
// Store path so it will allways persist if default location
|
|
||||||
// will be changed in new version.
|
|
||||||
b.Settings.Set(settings.CacheLocationKey, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// To prevent memory peaks we set maximal write concurency for store
|
|
||||||
// build jobs.
|
|
||||||
store.SetBuildAndCacheJobLimit(b.Settings.GetInt(settings.CacheConcurrencyWrite))
|
|
||||||
|
|
||||||
messageCache, err := cache.NewOnDiskCache(path, compressor, cache.Options{
|
|
||||||
MinFreeAbs: uint64(b.Settings.GetInt(settings.CacheMinFreeAbsKey)),
|
|
||||||
MinFreeRat: b.Settings.GetFloat64(settings.CacheMinFreeRatKey),
|
|
||||||
ConcurrentRead: b.Settings.GetInt(settings.CacheConcurrencyRead),
|
|
||||||
ConcurrentWrite: b.Settings.GetInt(settings.CacheConcurrencyWrite),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return cache.NewInMemoryCache(inMemoryCacheLimnit), err
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageCache, nil
|
|
||||||
}
|
|
||||||
70
internal/app/frontend.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/crash"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
bridgeCLI "github.com/ProtonMail/proton-bridge/v3/internal/frontend/cli"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/frontend/grpc"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/restarter"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runFrontend(
|
||||||
|
c *cli.Context,
|
||||||
|
crashHandler *crash.Handler,
|
||||||
|
restarter *restarter.Restarter,
|
||||||
|
locations *locations.Locations,
|
||||||
|
bridge *bridge.Bridge,
|
||||||
|
eventCh <-chan events.Event,
|
||||||
|
quitCh <-chan struct{},
|
||||||
|
parentPID int,
|
||||||
|
) error {
|
||||||
|
logrus.Debug("Running frontend")
|
||||||
|
defer logrus.Debug("Frontend stopped")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case c.Bool(flagCLI):
|
||||||
|
return bridgeCLI.New(bridge, restarter, eventCh, crashHandler, quitCh).Loop()
|
||||||
|
|
||||||
|
case c.Bool(flagNonInteractive):
|
||||||
|
<-quitCh
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case c.Bool(flagGRPC):
|
||||||
|
service, err := grpc.NewService(crashHandler, restarter, locations, bridge, eventCh, quitCh, !c.Bool(flagNoWindow), parentPID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return service.Loop()
|
||||||
|
|
||||||
|
default:
|
||||||
|
if err := cli.ShowAppHelp(c); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to show app help")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("no frontend specified, use --cli, --grpc or --noninteractive")
|
||||||
|
}
|
||||||
|
}
|
||||||
371
internal/app/migration.go
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/legacy/credentials"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/algo"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
|
"github.com/allan-simon/go-singleinstance"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint:gosec
|
||||||
|
func migrateKeychainHelper(locations *locations.Locations) error {
|
||||||
|
logrus.Info("Migrating keychain helper")
|
||||||
|
|
||||||
|
settings, err := locations.ProvideSettingsPath()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get settings path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If keychain helper file is already there do not migrate again.
|
||||||
|
if keychainName, _ := vault.GetHelper(settings); keychainName != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get user config dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := os.ReadFile(filepath.Join(configDir, "protonmail", "bridge", "prefs.json"))
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("failed to read old prefs file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefs struct {
|
||||||
|
Helper string `json:"preferred_keychain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(b, &prefs); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal old prefs file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vault.SetHelper(settings, prefs.Helper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:gosec
|
||||||
|
func migrateOldSettings(v *vault.Vault) error {
|
||||||
|
logrus.Info("Migrating settings")
|
||||||
|
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get user config dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return migrateOldSettingsWithDir(configDir, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:gosec
|
||||||
|
func migrateOldSettingsWithDir(configDir string, v *vault.Vault) error {
|
||||||
|
b, err := os.ReadFile(filepath.Join(configDir, "protonmail", "bridge", "prefs.json"))
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("failed to read old prefs file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := migratePrefsToVault(v, b); err != nil {
|
||||||
|
return fmt.Errorf("failed to migrate prefs to vault: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Info("Migrating TLS certificate")
|
||||||
|
|
||||||
|
certPEM, err := os.ReadFile(filepath.Join(configDir, "protonmail", "bridge", "cert.pem"))
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("failed to read old cert file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPEM, err := os.ReadFile(filepath.Join(configDir, "protonmail", "bridge", "key.pem"))
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("failed to read old key file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.SetBridgeTLSCertKey(certPEM, keyPEM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateOldAccounts(locations *locations.Locations, keychains *keychain.List, v *vault.Vault) error {
|
||||||
|
logrus.Info("Migrating accounts")
|
||||||
|
|
||||||
|
settings, err := locations.ProvideSettingsPath()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get settings path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
helper, err := vault.GetHelper(settings)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get helper: %w", err)
|
||||||
|
}
|
||||||
|
keychain, err := keychain.NewKeychain(helper, "bridge", keychains.GetHelpers(), keychains.GetDefaultHelper())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create keychain: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
store := credentials.NewStore(keychain)
|
||||||
|
|
||||||
|
users, err := store.List()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create credentials store: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var migrationErrors error
|
||||||
|
|
||||||
|
for _, userID := range users {
|
||||||
|
if err := migrateOldAccount(userID, store, v); err != nil {
|
||||||
|
migrationErrors = multierror.Append(migrationErrors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return migrationErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateOldAccount(userID string, store *credentials.Store, v *vault.Vault) error {
|
||||||
|
l := logrus.WithField("userID", userID)
|
||||||
|
l.Info("Migrating account")
|
||||||
|
|
||||||
|
creds, err := store.Get(userID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get user %q: %w", userID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authUID, authRef, err := creds.SplitAPIToken()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to split api token for user %q: %w", userID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var primaryEmail string
|
||||||
|
if len(creds.EmailList()) > 0 {
|
||||||
|
primaryEmail = creds.EmailList()[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := v.AddUser(creds.UserID, creds.Name, primaryEmail, authUID, authRef, creds.MailboxPassword)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add user %q: %w", userID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l = l.WithField("username", logging.Sensitive(user.Username()))
|
||||||
|
l.Info("Migrated account with random bridge password")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := user.Close(); err != nil {
|
||||||
|
logrus.WithField("userID", userID).WithError(err).Error("Failed to close vault user after migration")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
dec, err := algo.B64RawDecode([]byte(creds.BridgePassword))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode bridge password for user %q: %w", userID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := user.SetBridgePass(dec); err != nil {
|
||||||
|
return fmt.Errorf("failed to set bridge password for user %q: %w", userID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l = l.WithField("password", logging.Sensitive(string(algo.B64RawEncode(dec))))
|
||||||
|
l.Info("Migrated existing bridge password")
|
||||||
|
|
||||||
|
if !creds.IsCombinedAddressMode {
|
||||||
|
if err := user.SetAddressMode(vault.SplitMode); err != nil {
|
||||||
|
return fmt.Errorf("failed to set split address mode to user %q: %w", userID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func migratePrefsToVault(vault *vault.Vault, b []byte) error {
|
||||||
|
var prefs struct {
|
||||||
|
IMAPPort int `json:"user_port_imap,,string"`
|
||||||
|
SMTPPort int `json:"user_port_smtp,,string"`
|
||||||
|
SMTPSSL bool `json:"user_ssl_smtp,,string"`
|
||||||
|
|
||||||
|
AutoUpdate bool `json:"autoupdate,,string"`
|
||||||
|
UpdateChannel updater.Channel `json:"update_channel"`
|
||||||
|
UpdateRollout float64 `json:"rollout,,string"`
|
||||||
|
|
||||||
|
FirstStart bool `json:"first_time_start,,string"`
|
||||||
|
ColorScheme string `json:"color_scheme"`
|
||||||
|
LastVersion *semver.Version `json:"last_used_version"`
|
||||||
|
Autostart bool `json:"autostart,,string"`
|
||||||
|
|
||||||
|
AllowProxy bool `json:"allow_proxy,,string"`
|
||||||
|
FetchWorkers int `json:"fetch_workers,,string"`
|
||||||
|
AttachmentWorkers int `json:"attachment_workers,,string"`
|
||||||
|
ShowAllMail bool `json:"is_all_mail_visible,,string"`
|
||||||
|
|
||||||
|
Cookies string `json:"cookies"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(b, &prefs); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal old prefs file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs error
|
||||||
|
|
||||||
|
if err := vault.SetIMAPPort(prefs.IMAPPort); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate IMAP port: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetSMTPPort(prefs.SMTPPort); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate SMTP port: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetSMTPSSL(prefs.SMTPSSL); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate SMTP SSL: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetAutoUpdate(prefs.AutoUpdate); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate auto update: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetUpdateChannel(prefs.UpdateChannel); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate update channel: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetUpdateRollout(prefs.UpdateRollout); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate rollout: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetFirstStart(prefs.FirstStart); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate first start: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetColorScheme(prefs.ColorScheme); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate color scheme: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetLastVersion(prefs.LastVersion); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate last version: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetAutostart(prefs.Autostart); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate autostart: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetProxyAllowed(prefs.AllowProxy); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate allow proxy: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetShowAllMail(prefs.ShowAllMail); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate show all mail: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetCookies([]byte(prefs.Cookies)); err != nil {
|
||||||
|
errs = multierror.Append(errs, fmt.Errorf("failed to migrate cookies: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateOldVersions() (allErrors error) {
|
||||||
|
cacheDir, cacheError := os.UserCacheDir()
|
||||||
|
if cacheError != nil {
|
||||||
|
allErrors = multierror.Append(allErrors, errors.Wrap(cacheError, "cannot get os cache"))
|
||||||
|
return // not need to continue for now (with more migrations might be still ok to continue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := killV2AppAndRemoveV2LockFiles(filepath.Join(cacheDir, "protonmail", "bridge", "bridge.lock")); err != nil {
|
||||||
|
allErrors = multierror.Append(allErrors, errors.Wrap(err, "cannot migrate lockfiles"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func killV2AppAndRemoveV2LockFiles(lockFilePathV2 string) error {
|
||||||
|
l := logrus.WithField("path", lockFilePathV2)
|
||||||
|
|
||||||
|
if _, err := os.Stat(lockFilePathV2); os.IsNotExist(err) {
|
||||||
|
l.Debug("no v2 lockfile")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lock, err := singleinstance.CreateLockFile(lockFilePathV2)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
l.Debug("no other v2 instance is running")
|
||||||
|
|
||||||
|
if errClose := lock.Close(); errClose != nil {
|
||||||
|
l.WithError(errClose).Error("Cannot close lock file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Remove(lockFilePathV2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The other instance is an older version, so we should kill it.
|
||||||
|
pid, err := getPID(lockFilePathV2)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "cannot get v2 pid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := killPID(pid); err != nil {
|
||||||
|
return errors.Wrapf(err, "cannot kill v2 app (PID %d)", pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to wait some time to release file lock
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPID(lockFilePath string) (int, error) {
|
||||||
|
file, err := os.Open(filepath.Clean(lockFilePath))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
rawPID := make([]byte, 10) // PID is probably up to 7 digits long, 10 should be enough
|
||||||
|
n, err := file.Read(rawPID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strconv.Atoi(strings.TrimSpace(string(rawPID[:n])))
|
||||||
|
}
|
||||||
|
|
||||||
|
func killPID(pid int) error {
|
||||||
|
p, err := os.FindProcess(pid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Kill()
|
||||||
|
}
|
||||||
227
internal/app/migration_test.go
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gluon/async"
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/cookies"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/legacy/credentials"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/algo"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMigratePrefsToVaultWithKeys(t *testing.T) {
|
||||||
|
// Create a new vault.
|
||||||
|
vault, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key"), async.NoopPanicHandler{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, corrupt)
|
||||||
|
|
||||||
|
// load the old prefs file.
|
||||||
|
configDir := filepath.Join("testdata", "with_keys")
|
||||||
|
|
||||||
|
// Migrate the old prefs file to the new vault.
|
||||||
|
require.NoError(t, migrateOldSettingsWithDir(configDir, vault))
|
||||||
|
|
||||||
|
// Check Json Settings
|
||||||
|
validateJSONPrefs(t, vault)
|
||||||
|
|
||||||
|
cert, key := vault.GetBridgeTLSCert()
|
||||||
|
// Check the keys were found and collected.
|
||||||
|
require.Equal(t, "-----BEGIN CERTIFICATE-----", string(cert))
|
||||||
|
require.Equal(t, "-----BEGIN RSA PRIVATE KEY-----", string(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigratePrefsToVaultWithoutKeys(t *testing.T) {
|
||||||
|
// Create a new vault.
|
||||||
|
vault, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key"), async.NoopPanicHandler{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, corrupt)
|
||||||
|
|
||||||
|
// load the old prefs file.
|
||||||
|
configDir := filepath.Join("testdata", "without_keys")
|
||||||
|
|
||||||
|
// Migrate the old prefs file to the new vault.
|
||||||
|
require.NoError(t, migrateOldSettingsWithDir(configDir, vault))
|
||||||
|
|
||||||
|
// Migrate the old prefs file to the new vault.
|
||||||
|
require.NoError(t, migrateOldSettingsWithDir(configDir, vault))
|
||||||
|
|
||||||
|
// Check Json Settings
|
||||||
|
validateJSONPrefs(t, vault)
|
||||||
|
|
||||||
|
// Check the keys were found and collected.
|
||||||
|
cert, key := vault.GetBridgeTLSCert()
|
||||||
|
require.NotEqual(t, []byte("-----BEGIN CERTIFICATE-----"), cert)
|
||||||
|
require.NotEqual(t, []byte("-----BEGIN RSA PRIVATE KEY-----"), key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeychainMigration(t *testing.T) {
|
||||||
|
// Migration tested only for linux.
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
// Prepare for keychain migration test
|
||||||
|
{
|
||||||
|
require.NoError(t, os.Setenv("XDG_CONFIG_HOME", tmpDir))
|
||||||
|
oldCacheDir := filepath.Join(tmpDir, "protonmail", "bridge")
|
||||||
|
require.NoError(t, os.MkdirAll(oldCacheDir, 0o700))
|
||||||
|
|
||||||
|
oldPrefs, err := os.ReadFile(filepath.Join("testdata", "without_keys", "protonmail", "bridge", "prefs.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NoError(t, os.WriteFile(
|
||||||
|
filepath.Join(oldCacheDir, "prefs.json"),
|
||||||
|
oldPrefs, 0o600,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
locations := locations.New(bridge.NewTestLocationsProvider(tmpDir), "config-name")
|
||||||
|
settingsFolder, err := locations.ProvideSettingsPath()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check that there is nothing yet
|
||||||
|
keychainName, err := vault.GetHelper(settingsFolder)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "", keychainName)
|
||||||
|
|
||||||
|
// Check migration
|
||||||
|
require.NoError(t, migrateKeychainHelper(locations))
|
||||||
|
keychainName, err = vault.GetHelper(settingsFolder)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "secret-service", keychainName)
|
||||||
|
|
||||||
|
// Change the migrated value
|
||||||
|
require.NoError(t, vault.SetHelper(settingsFolder, "different"))
|
||||||
|
|
||||||
|
// Calling migration again will not overwrite existing prefs
|
||||||
|
require.NoError(t, migrateKeychainHelper(locations))
|
||||||
|
keychainName, err = vault.GetHelper(settingsFolder)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "different", keychainName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserMigration(t *testing.T) {
|
||||||
|
kcl := keychain.NewTestKeychainsList()
|
||||||
|
|
||||||
|
kc, err := keychain.NewKeychain("mock", "bridge", kcl.GetHelpers(), kcl.GetDefaultHelper())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NoError(t, kc.Put("brokenID", "broken"))
|
||||||
|
require.NoError(t, kc.Put(
|
||||||
|
"emptyID",
|
||||||
|
(&credentials.Credentials{}).Marshal(),
|
||||||
|
))
|
||||||
|
|
||||||
|
wantUID := "uidtoken"
|
||||||
|
wantRefresh := "refreshtoken"
|
||||||
|
|
||||||
|
wantCredentials := credentials.Credentials{
|
||||||
|
UserID: "validID",
|
||||||
|
Name: "user@pm.me",
|
||||||
|
Emails: "user@pm.me;alias@pm.me",
|
||||||
|
APIToken: wantUID + ":" + wantRefresh,
|
||||||
|
MailboxPassword: []byte("secret"),
|
||||||
|
BridgePassword: "bElu2Q1Vusy28J3Wf56cIg",
|
||||||
|
Version: "v2.3.X",
|
||||||
|
Timestamp: 100,
|
||||||
|
IsCombinedAddressMode: true,
|
||||||
|
}
|
||||||
|
require.NoError(t, kc.Put(
|
||||||
|
wantCredentials.UserID,
|
||||||
|
wantCredentials.Marshal(),
|
||||||
|
))
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
locations := locations.New(bridge.NewTestLocationsProvider(tmpDir), "config-name")
|
||||||
|
settingsFolder, err := locations.ProvideSettingsPath()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, vault.SetHelper(settingsFolder, "mock"))
|
||||||
|
|
||||||
|
token, err := crypto.RandomToken(32)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
v, corrupt, err := vault.New(settingsFolder, settingsFolder, token, async.NoopPanicHandler{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, corrupt)
|
||||||
|
|
||||||
|
require.NoError(t, migrateOldAccounts(locations, kcl, v))
|
||||||
|
require.Equal(t, []string{wantCredentials.UserID}, v.GetUserIDs())
|
||||||
|
|
||||||
|
require.NoError(t, v.GetUser(wantCredentials.UserID, func(u *vault.User) {
|
||||||
|
require.Equal(t, wantCredentials.UserID, u.UserID())
|
||||||
|
require.Equal(t, wantUID, u.AuthUID())
|
||||||
|
require.Equal(t, wantRefresh, u.AuthRef())
|
||||||
|
require.Equal(t, wantCredentials.MailboxPassword, u.KeyPass())
|
||||||
|
require.Equal(t,
|
||||||
|
[]byte(wantCredentials.BridgePassword),
|
||||||
|
algo.B64RawEncode(u.BridgePass()),
|
||||||
|
)
|
||||||
|
require.Equal(t, vault.CombinedMode, u.AddressMode())
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateJSONPrefs(t *testing.T, vault *vault.Vault) {
|
||||||
|
// Check that the IMAP and SMTP prefs are migrated.
|
||||||
|
require.Equal(t, 2143, vault.GetIMAPPort())
|
||||||
|
require.Equal(t, 2025, vault.GetSMTPPort())
|
||||||
|
require.True(t, vault.GetSMTPSSL())
|
||||||
|
|
||||||
|
// Check that the update channel is migrated.
|
||||||
|
require.True(t, vault.GetAutoUpdate())
|
||||||
|
require.Equal(t, updater.EarlyChannel, vault.GetUpdateChannel())
|
||||||
|
require.Equal(t, 0.4849529004202015, vault.GetUpdateRollout())
|
||||||
|
|
||||||
|
// Check that the app settings have been migrated.
|
||||||
|
require.False(t, vault.GetFirstStart())
|
||||||
|
require.Equal(t, "blablabla", vault.GetColorScheme())
|
||||||
|
require.Equal(t, "2.3.0+git", vault.GetLastVersion().String())
|
||||||
|
require.True(t, vault.GetAutostart())
|
||||||
|
|
||||||
|
// Check that the other app settings have been migrated.
|
||||||
|
require.False(t, vault.GetProxyAllowed())
|
||||||
|
require.False(t, vault.GetShowAllMail())
|
||||||
|
|
||||||
|
// Check that the cookies have been migrated.
|
||||||
|
jar, err := cookiejar.New(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cookies, err := cookies.NewCookieJar(jar, vault)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
url, err := url.Parse("https://api.protonmail.ch")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// There should be a cookie for the API.
|
||||||
|
require.NotEmpty(t, cookies.Cookies(url))
|
||||||
|
}
|
||||||
70
internal/app/singleinstance.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
|
||||||
|
"github.com/allan-simon/go-singleinstance"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkSingleInstance checks if another instance of the application is already running.
|
||||||
|
// It tries to create a lock file at the given path.
|
||||||
|
// If it succeeds, it returns the lock file and a nil error.
|
||||||
|
//
|
||||||
|
// For macOS and Linux when already running version is older than this instance
|
||||||
|
// it will kill old and continue with this new bridge (i.e. no error returned).
|
||||||
|
func checkSingleInstance(settingPath, lockFilePath string, curVersion *semver.Version) (*os.File, error) {
|
||||||
|
if lock, err := singleinstance.CreateLockFile(lockFilePath); err == nil {
|
||||||
|
logrus.WithField("path", lockFilePath).Debug("Created lock file; no other instance is running")
|
||||||
|
return lock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Warn("Failed to create lock file; another instance is running")
|
||||||
|
|
||||||
|
// We couldn't create the lock file, so another instance is probably running.
|
||||||
|
// Check if it's an older version of the app.
|
||||||
|
lastVersion, ok := focus.TryVersion(settingPath)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to determine version of running instance")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lastVersion.LessThan(curVersion) {
|
||||||
|
return nil, fmt.Errorf("running instance is newer than this one")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The other instance is an older version, so we should kill it.
|
||||||
|
pid, err := getPID(lockFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := killPID(pid); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to wait some time to release file lock
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
return singleinstance.CreateLockFile(lockFilePath)
|
||||||
|
}
|
||||||
1
internal/app/testdata/with_keys/protonmail/bridge/cert.pem
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
1
internal/app/testdata/with_keys/protonmail/bridge/key.pem
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
31
internal/app/testdata/with_keys/protonmail/bridge/prefs.json
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"allow_proxy": "false",
|
||||||
|
"attachment_workers": "16",
|
||||||
|
"autostart": "true",
|
||||||
|
"autoupdate": "true",
|
||||||
|
"cache_compression": "true",
|
||||||
|
"cache_concurrent_read": "16",
|
||||||
|
"cache_concurrent_write": "16",
|
||||||
|
"cache_enabled": "true",
|
||||||
|
"cache_location": "/home/user/.config/protonmail/bridge/cache/c11/messages",
|
||||||
|
"cache_min_free_abs": "250000000",
|
||||||
|
"cache_min_free_rat": "",
|
||||||
|
"color_scheme": "blablabla",
|
||||||
|
"cookies": "{\"https://api.protonmail.ch\":[{\"Name\":\"Session-Id\",\"Value\":\"blablablablablablablablabla\",\"Path\":\"/\",\"Domain\":\"protonmail.ch\",\"Expires\":\"2023-02-19T00:20:40.269424437+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":true,\"SameSite\":0,\"Raw\":\"Session-Id=blablablablablablablablabla; Domain=protonmail.ch; Path=/; HttpOnly; Secure; Max-Age=7776000\",\"Unparsed\":null},{\"Name\":\"Tag\",\"Value\":\"default\",\"Path\":\"/\",\"Domain\":\"\",\"Expires\":\"2023-02-19T00:20:40.269428627+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":false,\"SameSite\":0,\"Raw\":\"Tag=default; Path=/; Secure; Max-Age=7776000\",\"Unparsed\":null}],\"https://protonmail.com\":[{\"Name\":\"Session-Id\",\"Value\":\"blablablablablablablablabla\",\"Path\":\"/\",\"Domain\":\"protonmail.com\",\"Expires\":\"2023-02-19T00:20:18.315084712+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":true,\"SameSite\":0,\"Raw\":\"Session-Id=Y3q2Mh-ClvqL6LWeYdfyPgAAABI; Domain=protonmail.com; Path=/; HttpOnly; Secure; Max-Age=7776000\",\"Unparsed\":null},{\"Name\":\"Tag\",\"Value\":\"redirect\",\"Path\":\"/\",\"Domain\":\"\",\"Expires\":\"2023-02-19T00:20:18.315087646+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":false,\"SameSite\":0,\"Raw\":\"Tag=redirect; Path=/; Secure; Max-Age=7776000\",\"Unparsed\":null}]}",
|
||||||
|
"fetch_workers": "16",
|
||||||
|
"first_time_start": "false",
|
||||||
|
"first_time_start_gui": "true",
|
||||||
|
"imap_workers": "16",
|
||||||
|
"is_all_mail_visible": "false",
|
||||||
|
"last_heartbeat": "325",
|
||||||
|
"last_used_version": "2.3.0+git",
|
||||||
|
"preferred_keychain": "secret-service",
|
||||||
|
"rebranding_migrated": "true",
|
||||||
|
"report_outgoing_email_without_encryption": "false",
|
||||||
|
"rollout": "0.4849529004202015",
|
||||||
|
"user_port_api": "1042",
|
||||||
|
"update_channel": "early",
|
||||||
|
"user_port_imap": "2143",
|
||||||
|
"user_port_smtp": "2025",
|
||||||
|
"user_ssl_smtp": "true"
|
||||||
|
}
|
||||||
31
internal/app/testdata/without_keys/protonmail/bridge/prefs.json
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"allow_proxy": "false",
|
||||||
|
"attachment_workers": "16",
|
||||||
|
"autostart": "true",
|
||||||
|
"autoupdate": "true",
|
||||||
|
"cache_compression": "true",
|
||||||
|
"cache_concurrent_read": "16",
|
||||||
|
"cache_concurrent_write": "16",
|
||||||
|
"cache_enabled": "true",
|
||||||
|
"cache_location": "/home/user/.config/protonmail/bridge/cache/c11/messages",
|
||||||
|
"cache_min_free_abs": "250000000",
|
||||||
|
"cache_min_free_rat": "",
|
||||||
|
"color_scheme": "blablabla",
|
||||||
|
"cookies": "{\"https://api.protonmail.ch\":[{\"Name\":\"Session-Id\",\"Value\":\"blablablablablablablablabla\",\"Path\":\"/\",\"Domain\":\"protonmail.ch\",\"Expires\":\"2023-02-19T00:20:40.269424437+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":true,\"SameSite\":0,\"Raw\":\"Session-Id=blablablablablablablablabla; Domain=protonmail.ch; Path=/; HttpOnly; Secure; Max-Age=7776000\",\"Unparsed\":null},{\"Name\":\"Tag\",\"Value\":\"default\",\"Path\":\"/\",\"Domain\":\"\",\"Expires\":\"2023-02-19T00:20:40.269428627+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":false,\"SameSite\":0,\"Raw\":\"Tag=default; Path=/; Secure; Max-Age=7776000\",\"Unparsed\":null}],\"https://protonmail.com\":[{\"Name\":\"Session-Id\",\"Value\":\"blablablablablablablablabla\",\"Path\":\"/\",\"Domain\":\"protonmail.com\",\"Expires\":\"2023-02-19T00:20:18.315084712+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":true,\"SameSite\":0,\"Raw\":\"Session-Id=Y3q2Mh-ClvqL6LWeYdfyPgAAABI; Domain=protonmail.com; Path=/; HttpOnly; Secure; Max-Age=7776000\",\"Unparsed\":null},{\"Name\":\"Tag\",\"Value\":\"redirect\",\"Path\":\"/\",\"Domain\":\"\",\"Expires\":\"2023-02-19T00:20:18.315087646+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":false,\"SameSite\":0,\"Raw\":\"Tag=redirect; Path=/; Secure; Max-Age=7776000\",\"Unparsed\":null}]}",
|
||||||
|
"fetch_workers": "16",
|
||||||
|
"first_time_start": "false",
|
||||||
|
"first_time_start_gui": "true",
|
||||||
|
"imap_workers": "16",
|
||||||
|
"is_all_mail_visible": "false",
|
||||||
|
"last_heartbeat": "325",
|
||||||
|
"last_used_version": "2.3.0+git",
|
||||||
|
"preferred_keychain": "secret-service",
|
||||||
|
"rebranding_migrated": "true",
|
||||||
|
"report_outgoing_email_without_encryption": "false",
|
||||||
|
"rollout": "0.4849529004202015",
|
||||||
|
"user_port_api": "1042",
|
||||||
|
"update_channel": "early",
|
||||||
|
"user_port_imap": "2143",
|
||||||
|
"user_port_smtp": "2025",
|
||||||
|
"user_ssl_smtp": "true"
|
||||||
|
}
|
||||||
117
internal/app/vault.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gluon/async"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
|
||||||
|
logrus.Debug("Creating vault")
|
||||||
|
defer logrus.Debug("Vault stopped")
|
||||||
|
|
||||||
|
// Create the encVault.
|
||||||
|
encVault, insecure, corrupt, err := newVault(locations, keychains, panicHandler)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create vault: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"insecure": insecure,
|
||||||
|
"corrupt": corrupt != nil,
|
||||||
|
}).Debug("Vault created")
|
||||||
|
|
||||||
|
if corrupt != nil {
|
||||||
|
logrus.WithError(corrupt).Warn("Failed to load existing vault, vault has been reset")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, _ := encVault.GetBridgeTLSCert()
|
||||||
|
certs.NewInstaller().LogCertInstallStatus(cert)
|
||||||
|
|
||||||
|
// GODT-1950: Add teardown actions (e.g. to close the vault).
|
||||||
|
|
||||||
|
return fn(encVault, insecure, corrupt != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler) (*vault.Vault, bool, error, error) {
|
||||||
|
vaultDir, err := locations.ProvideSettingsPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, nil, fmt.Errorf("could not get vault dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithField("vaultDir", vaultDir).Debug("Loading vault from directory")
|
||||||
|
|
||||||
|
var (
|
||||||
|
vaultKey []byte
|
||||||
|
insecure bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if key, err := loadVaultKey(vaultDir, keychains); err != nil {
|
||||||
|
logrus.WithError(err).Error("Could not load/create vault key")
|
||||||
|
insecure = true
|
||||||
|
|
||||||
|
// We store the insecure vault in a separate directory
|
||||||
|
vaultDir = path.Join(vaultDir, "insecure")
|
||||||
|
} else {
|
||||||
|
vaultKey = key
|
||||||
|
}
|
||||||
|
|
||||||
|
gluonCacheDir, err := locations.ProvideGluonCachePath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, nil, fmt.Errorf("could not provide gluon path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vault, corrupt, err := vault.New(vaultDir, gluonCacheDir, vaultKey, panicHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, corrupt, fmt.Errorf("could not create vault: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vault, insecure, corrupt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadVaultKey(vaultDir string, keychains *keychain.List) ([]byte, error) {
|
||||||
|
helper, err := vault.GetHelper(vaultDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not get keychain helper: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kc, err := keychain.NewKeychain(helper, constants.KeyChainName, keychains.GetHelpers(), keychains.GetDefaultHelper())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create keychain: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
has, err := vault.HasVaultKey(kc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not check for vault key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if has {
|
||||||
|
return vault.GetVaultKey(kc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vault.NewVaultKey(kc)
|
||||||
|
}
|
||||||
46
internal/bridge/api.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/gluon/async"
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultAPIOptions returns a set of default API options for the given parameters.
|
||||||
|
func defaultAPIOptions(
|
||||||
|
apiURL string,
|
||||||
|
version *semver.Version,
|
||||||
|
cookieJar http.CookieJar,
|
||||||
|
transport http.RoundTripper,
|
||||||
|
panicHandler async.PanicHandler,
|
||||||
|
) []proton.Option {
|
||||||
|
return []proton.Option{
|
||||||
|
proton.WithHostURL(apiURL),
|
||||||
|
proton.WithAppVersion(constants.AppVersion(version.Original())),
|
||||||
|
proton.WithCookieJar(cookieJar),
|
||||||
|
proton.WithTransport(transport),
|
||||||
|
proton.WithLogger(logrus.StandardLogger()),
|
||||||
|
proton.WithPanicHandler(panicHandler),
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
// Copyright (c) 2023 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -13,24 +13,27 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
//go:build !build_qa
|
//go:build !build_qa && !test_integration
|
||||||
// +build !build_qa
|
|
||||||
|
|
||||||
package pmapi
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/gluon/async"
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getRootURL() string {
|
// newAPIOptions returns a set of API options for the given parameters.
|
||||||
return "https://api.protonmail.ch"
|
func newAPIOptions(
|
||||||
}
|
apiURL string,
|
||||||
|
version *semver.Version,
|
||||||
func newProxyDialerAndTransport(cfg Config) (*ProxyTLSDialer, http.RoundTripper) {
|
cookieJar http.CookieJar,
|
||||||
basicDialer := NewBasicTLSDialer(cfg)
|
transport http.RoundTripper,
|
||||||
pinningDialer := NewPinningTLSDialer(cfg, basicDialer)
|
panicHandler async.PanicHandler,
|
||||||
proxyDialer := NewProxyTLSDialer(cfg, pinningDialer)
|
) []proton.Option {
|
||||||
return proxyDialer, CreateTransportWithDialer(proxyDialer)
|
return defaultAPIOptions(apiURL, version, cookieJar, transport, panicHandler)
|
||||||
}
|
}
|
||||||
63
internal/bridge/api_qa.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build build_qa || test_integration
|
||||||
|
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/gluon/async"
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newAPIOptions returns a set of API options for the given parameters.
|
||||||
|
func newAPIOptions(
|
||||||
|
apiURL string,
|
||||||
|
version *semver.Version,
|
||||||
|
cookieJar http.CookieJar,
|
||||||
|
transport http.RoundTripper,
|
||||||
|
panicHandler async.PanicHandler,
|
||||||
|
) []proton.Option {
|
||||||
|
|
||||||
|
if allow := os.Getenv("BRIDGE_ALLOW_PROXY"); allow != "" {
|
||||||
|
transport = &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := defaultAPIOptions(apiURL, version, cookieJar, transport, panicHandler)
|
||||||
|
|
||||||
|
if host := os.Getenv("BRIDGE_API_HOST"); host != "" {
|
||||||
|
opt = append(opt, proton.WithHostURL(host))
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug := os.Getenv("BRIDGE_API_DEBUG"); debug != "" {
|
||||||
|
opt = append(opt, proton.WithDebug(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
if skipVerify := os.Getenv("BRIDGE_API_SKIP_VERIFY"); skipVerify != "" {
|
||||||
|
opt = append(opt, proton.WithSkipVerifyProofs())
|
||||||
|
}
|
||||||
|
|
||||||
|
return opt
|
||||||
|
}
|
||||||
@ -1,38 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Package bridge provides core functionality of Bridge app.
|
|
||||||
package bridge
|
|
||||||
|
|
||||||
import "github.com/ProtonMail/proton-bridge/internal/config/settings"
|
|
||||||
|
|
||||||
// IsAutostartEnabled checks if link file exits.
|
|
||||||
func (b *Bridge) IsAutostartEnabled() bool {
|
|
||||||
return b.autostart.IsEnabled()
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableAutostart creates link and sets the preferences.
|
|
||||||
func (b *Bridge) EnableAutostart() error {
|
|
||||||
b.settings.SetBool(settings.AutostartKey, true)
|
|
||||||
return b.autostart.Enable()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableAutostart removes link and sets the preferences.
|
|
||||||
func (b *Bridge) DisableAutostart() error {
|
|
||||||
b.settings.SetBool(settings.AutostartKey, false)
|
|
||||||
return b.autostart.Disable()
|
|
||||||
}
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
// Copyright (c) 2023 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -13,292 +13,546 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package bridge provides core functionality of Bridge app.
|
// Package bridge implements the Bridge, which acts as the backend to the UI.
|
||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/ProtonMail/go-autostart"
|
"github.com/ProtonMail/gluon/async"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
imapEvents "github.com/ProtonMail/gluon/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
"github.com/ProtonMail/gluon/imap"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
"github.com/ProtonMail/gluon/reporter"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/sentry"
|
"github.com/ProtonMail/gluon/watcher"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/store/cache"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/message"
|
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/v3/internal/identifier"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||||
logrus "github.com/sirupsen/logrus"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
|
"github.com/bradenaw/juniper/xslices"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logrus.WithField("pkg", "bridge") //nolint:gochecknoglobals
|
|
||||||
|
|
||||||
var ErrLocalCacheUnavailable = errors.New("local cache is unavailable")
|
|
||||||
|
|
||||||
type Bridge struct {
|
type Bridge struct {
|
||||||
*users.Users
|
// vault holds bridge-specific data, such as preferences and known users (authorized or not).
|
||||||
|
vault *vault.Vault
|
||||||
|
|
||||||
locations Locator
|
// users holds authorized users.
|
||||||
settings SettingsProvider
|
users map[string]*user.User
|
||||||
clientManager pmapi.Manager
|
usersLock safe.RWMutex
|
||||||
updater Updater
|
|
||||||
versioner Versioner
|
// api manages user API clients.
|
||||||
cacheProvider CacheProvider
|
api *proton.Manager
|
||||||
autostart *autostart.App
|
proxyCtl ProxyController
|
||||||
// Bridge's global errors list.
|
identifier identifier.Identifier
|
||||||
|
|
||||||
|
// tlsConfig holds the bridge TLS config used by the IMAP and SMTP servers.
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
|
||||||
|
// imapServer is the bridge's IMAP server.
|
||||||
|
imapEventCh chan imapEvents.Event
|
||||||
|
|
||||||
|
// updater is the bridge's updater.
|
||||||
|
updater Updater
|
||||||
|
installCh chan installJob
|
||||||
|
|
||||||
|
// heartbeat is the telemetry heartbeat for metrics.
|
||||||
|
heartbeat *heartBeatState
|
||||||
|
|
||||||
|
// curVersion is the current version of the bridge,
|
||||||
|
// newVersion is the version that was installed by the updater.
|
||||||
|
curVersion *semver.Version
|
||||||
|
newVersion *semver.Version
|
||||||
|
newVersionLock safe.RWMutex
|
||||||
|
|
||||||
|
// keychains is the utils that own usable keychains found in the OS.
|
||||||
|
keychains *keychain.List
|
||||||
|
|
||||||
|
// focusService is used to raise the bridge window when needed.
|
||||||
|
focusService *focus.Service
|
||||||
|
|
||||||
|
// autostarter is the bridge's autostarter.
|
||||||
|
autostarter Autostarter
|
||||||
|
|
||||||
|
// locator is the bridge's locator.
|
||||||
|
locator Locator
|
||||||
|
|
||||||
|
// panicHandler
|
||||||
|
panicHandler async.PanicHandler
|
||||||
|
|
||||||
|
// reporter
|
||||||
|
reporter reporter.Reporter
|
||||||
|
|
||||||
|
// watchers holds all registered event watchers.
|
||||||
|
watchers []*watcher.Watcher[events.Event]
|
||||||
|
watchersLock sync.RWMutex
|
||||||
|
|
||||||
|
// errors contains errors encountered during startup.
|
||||||
errors []error
|
errors []error
|
||||||
|
|
||||||
isFirstStart bool
|
// These control the bridge's IMAP and SMTP logging behaviour.
|
||||||
lastVersion string
|
logIMAPClient bool
|
||||||
|
logIMAPServer bool
|
||||||
|
logSMTP bool
|
||||||
|
|
||||||
|
// These two variables keep track of the startup values for the two settings of the same name.
|
||||||
|
// They are updated in the vault on startup so that we're sure they're updated in case of kill/crash,
|
||||||
|
// but we need to keep their initial value for the current instance of bridge.
|
||||||
|
firstStart bool
|
||||||
|
lastVersion *semver.Version
|
||||||
|
|
||||||
|
// tasks manages the bridge's goroutines.
|
||||||
|
tasks *async.Group
|
||||||
|
|
||||||
|
// goLoad triggers a load of disconnected users from the vault.
|
||||||
|
goLoad func()
|
||||||
|
|
||||||
|
// goUpdate triggers a check/install of updates.
|
||||||
|
goUpdate func()
|
||||||
|
|
||||||
|
serverManager *imapsmtpserver.Service
|
||||||
|
syncService *syncservice.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New creates a new bridge.
|
||||||
func New(
|
func New(
|
||||||
locations Locator,
|
locator Locator, // the locator to provide paths to store data
|
||||||
cacheProvider CacheProvider,
|
vault *vault.Vault, // the bridge's encrypted data store
|
||||||
setting SettingsProvider,
|
autostarter Autostarter, // the autostarter to manage autostart settings
|
||||||
sentryReporter *sentry.Reporter,
|
updater Updater, // the updater to fetch and install updates
|
||||||
panicHandler users.PanicHandler,
|
curVersion *semver.Version, // the current version of the bridge
|
||||||
eventListener listener.Listener,
|
keychains *keychain.List, // usable keychains
|
||||||
cache cache.Cache,
|
|
||||||
builder *message.Builder,
|
apiURL string, // the URL of the API to use
|
||||||
clientManager pmapi.Manager,
|
cookieJar http.CookieJar, // the cookie jar to use
|
||||||
credStorer users.CredentialsStorer,
|
identifier identifier.Identifier, // the identifier to keep track of the user agent
|
||||||
updater Updater,
|
tlsReporter TLSReporter, // the TLS reporter to report TLS errors
|
||||||
versioner Versioner,
|
roundTripper http.RoundTripper, // the round tripper to use for API requests
|
||||||
autostart *autostart.App,
|
proxyCtl ProxyController, // the DoH controller
|
||||||
) *Bridge {
|
panicHandler async.PanicHandler,
|
||||||
// Allow DoH before starting the app if the user has previously set this setting.
|
reporter reporter.Reporter,
|
||||||
// This allows us to start even if protonmail is blocked.
|
uidValidityGenerator imap.UIDValidityGenerator,
|
||||||
if setting.GetBool(settings.AllowProxyKey) {
|
heartBeatManager telemetry.HeartbeatManager,
|
||||||
clientManager.AllowProxy()
|
|
||||||
|
logIMAPClient, logIMAPServer bool, // whether to log IMAP client/server activity
|
||||||
|
logSMTP bool, // whether to log SMTP activity
|
||||||
|
) (*Bridge, <-chan events.Event, error) {
|
||||||
|
// api is the user's API manager.
|
||||||
|
api := proton.New(newAPIOptions(apiURL, curVersion, cookieJar, roundTripper, panicHandler)...)
|
||||||
|
|
||||||
|
// tasks holds all the bridge's background tasks.
|
||||||
|
tasks := async.NewGroup(context.Background(), panicHandler)
|
||||||
|
|
||||||
|
// imapEventCh forwards IMAP events from gluon instances to the bridge for processing.
|
||||||
|
imapEventCh := make(chan imapEvents.Event)
|
||||||
|
|
||||||
|
// bridge is the bridge.
|
||||||
|
bridge, err := newBridge(
|
||||||
|
context.Background(),
|
||||||
|
tasks,
|
||||||
|
imapEventCh,
|
||||||
|
|
||||||
|
locator,
|
||||||
|
vault,
|
||||||
|
autostarter,
|
||||||
|
updater,
|
||||||
|
curVersion,
|
||||||
|
keychains,
|
||||||
|
panicHandler,
|
||||||
|
reporter,
|
||||||
|
|
||||||
|
api,
|
||||||
|
identifier,
|
||||||
|
proxyCtl,
|
||||||
|
uidValidityGenerator,
|
||||||
|
heartBeatManager,
|
||||||
|
logIMAPClient, logIMAPServer, logSMTP,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create bridge: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u := users.New(
|
// Get an event channel for all events (individual events can be subscribed to later).
|
||||||
locations,
|
eventCh, _ := bridge.GetEvents()
|
||||||
|
|
||||||
|
// Initialize all of bridge's background tasks and operations.
|
||||||
|
if err := bridge.init(tlsReporter); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to initialize bridge: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bridge, eventCh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBridge(
|
||||||
|
ctx context.Context,
|
||||||
|
tasks *async.Group,
|
||||||
|
imapEventCh chan imapEvents.Event,
|
||||||
|
|
||||||
|
locator Locator,
|
||||||
|
vault *vault.Vault,
|
||||||
|
autostarter Autostarter,
|
||||||
|
updater Updater,
|
||||||
|
curVersion *semver.Version,
|
||||||
|
keychains *keychain.List,
|
||||||
|
panicHandler async.PanicHandler,
|
||||||
|
reporter reporter.Reporter,
|
||||||
|
|
||||||
|
api *proton.Manager,
|
||||||
|
identifier identifier.Identifier,
|
||||||
|
proxyCtl ProxyController,
|
||||||
|
uidValidityGenerator imap.UIDValidityGenerator,
|
||||||
|
heartbeatManager telemetry.HeartbeatManager,
|
||||||
|
|
||||||
|
logIMAPClient, logIMAPServer, logSMTP bool,
|
||||||
|
) (*Bridge, error) {
|
||||||
|
tlsConfig, err := loadTLSConfig(vault)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load TLS config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
firstStart := vault.GetFirstStart()
|
||||||
|
if err := vault.SetFirstStart(false); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to save first start indicator: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastVersion := vault.GetLastVersion()
|
||||||
|
if err := vault.SetLastVersion(curVersion); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to save last version indicator: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
identifier.SetClientString(vault.GetLastUserAgent())
|
||||||
|
|
||||||
|
focusService, err := focus.NewService(locator, curVersion, panicHandler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create focus service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge := &Bridge{
|
||||||
|
vault: vault,
|
||||||
|
|
||||||
|
users: make(map[string]*user.User),
|
||||||
|
usersLock: safe.NewRWMutex(),
|
||||||
|
|
||||||
|
api: api,
|
||||||
|
proxyCtl: proxyCtl,
|
||||||
|
identifier: identifier,
|
||||||
|
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
|
imapEventCh: imapEventCh,
|
||||||
|
|
||||||
|
updater: updater,
|
||||||
|
installCh: make(chan installJob),
|
||||||
|
|
||||||
|
curVersion: curVersion,
|
||||||
|
newVersion: curVersion,
|
||||||
|
newVersionLock: safe.NewRWMutex(),
|
||||||
|
|
||||||
|
keychains: keychains,
|
||||||
|
|
||||||
|
panicHandler: panicHandler,
|
||||||
|
reporter: reporter,
|
||||||
|
|
||||||
|
heartbeat: newHeartBeatState(ctx, panicHandler),
|
||||||
|
|
||||||
|
focusService: focusService,
|
||||||
|
autostarter: autostarter,
|
||||||
|
locator: locator,
|
||||||
|
|
||||||
|
logIMAPClient: logIMAPClient,
|
||||||
|
logIMAPServer: logIMAPServer,
|
||||||
|
logSMTP: logSMTP,
|
||||||
|
|
||||||
|
firstStart: firstStart,
|
||||||
|
lastVersion: lastVersion,
|
||||||
|
|
||||||
|
tasks: tasks,
|
||||||
|
syncService: syncservice.NewService(reporter, panicHandler),
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.serverManager = imapsmtpserver.NewService(context.Background(),
|
||||||
|
&bridgeSMTPSettings{b: bridge},
|
||||||
|
&bridgeIMAPSettings{b: bridge},
|
||||||
|
&bridgeEventPublisher{b: bridge},
|
||||||
panicHandler,
|
panicHandler,
|
||||||
eventListener,
|
reporter,
|
||||||
clientManager,
|
uidValidityGenerator,
|
||||||
credStorer,
|
&bridgeIMAPSMTPTelemetry{b: bridge},
|
||||||
newStoreFactory(cacheProvider, sentryReporter, panicHandler, eventListener, cache, builder),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
b := &Bridge{
|
if err := bridge.serverManager.Init(context.Background(), bridge.tasks, &bridgeEventSubscription{b: bridge}); err != nil {
|
||||||
Users: u,
|
return nil, err
|
||||||
locations: locations,
|
|
||||||
settings: setting,
|
|
||||||
clientManager: clientManager,
|
|
||||||
updater: updater,
|
|
||||||
versioner: versioner,
|
|
||||||
cacheProvider: cacheProvider,
|
|
||||||
autostart: autostart,
|
|
||||||
isFirstStart: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.GetBool(settings.FirstStartKey) {
|
if heartbeatManager == nil {
|
||||||
b.isFirstStart = true
|
bridge.heartbeat.init(bridge, bridge)
|
||||||
if err := b.SendMetric(metrics.New(metrics.Setup, metrics.FirstStart, metrics.Label(constants.Version))); err != nil {
|
} else {
|
||||||
logrus.WithError(err).Error("Failed to send metric")
|
bridge.heartbeat.init(bridge, heartbeatManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.syncService.Run()
|
||||||
|
|
||||||
|
return bridge, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
||||||
|
// Enable or disable the proxy at startup.
|
||||||
|
if bridge.vault.GetProxyAllowed() {
|
||||||
|
bridge.proxyCtl.AllowProxy()
|
||||||
|
} else {
|
||||||
|
bridge.proxyCtl.DisallowProxy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle connection up/down events.
|
||||||
|
bridge.api.AddStatusObserver(func(status proton.Status) {
|
||||||
|
logrus.Info("API status changed: ", status)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case status == proton.StatusUp:
|
||||||
|
bridge.publish(events.ConnStatusUp{})
|
||||||
|
bridge.tasks.Once(bridge.onStatusUp)
|
||||||
|
|
||||||
|
case status == proton.StatusDown:
|
||||||
|
bridge.publish(events.ConnStatusDown{})
|
||||||
|
bridge.tasks.Once(bridge.onStatusDown)
|
||||||
}
|
}
|
||||||
setting.SetBool(settings.FirstStartKey, false)
|
})
|
||||||
|
|
||||||
|
// If any call returns a bad version code, we need to update.
|
||||||
|
bridge.api.AddErrorHandler(proton.AppVersionBadCode, func() {
|
||||||
|
logrus.Warn("App version is bad")
|
||||||
|
bridge.publish(events.UpdateForced{})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Ensure all outgoing headers have the correct user agent.
|
||||||
|
bridge.api.AddPreRequestHook(func(_ *resty.Client, req *resty.Request) error {
|
||||||
|
req.SetHeader("User-Agent", bridge.identifier.GetUserAgent())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Log all manager API requests (client requests are logged separately).
|
||||||
|
bridge.api.AddPostRequestHook(func(_ *resty.Client, r *resty.Response) error {
|
||||||
|
if _, ok := proton.ClientIDFromContext(r.Request.Context()); !ok {
|
||||||
|
logrus.Infof("[MANAGER] %v: %v %v", r.Status(), r.Request.Method, r.Request.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Publish a TLS issue event if a TLS issue is encountered.
|
||||||
|
bridge.tasks.Once(func(ctx context.Context) {
|
||||||
|
async.RangeContext(ctx, tlsReporter.GetTLSIssueCh(), func(struct{}) {
|
||||||
|
logrus.Warn("TLS issue encountered")
|
||||||
|
bridge.publish(events.TLSIssue{})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Publish a raise event if the focus service is called.
|
||||||
|
bridge.tasks.Once(func(ctx context.Context) {
|
||||||
|
async.RangeContext(ctx, bridge.focusService.GetRaiseCh(), func(struct{}) {
|
||||||
|
logrus.Info("Focus service requested raise")
|
||||||
|
bridge.publish(events.Raise{})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle any IMAP events that are forwarded to the bridge from gluon.
|
||||||
|
bridge.tasks.Once(func(ctx context.Context) {
|
||||||
|
async.RangeContext(ctx, bridge.imapEventCh, func(event imapEvents.Event) {
|
||||||
|
logrus.WithField("event", fmt.Sprintf("%T", event)).Debug("Received IMAP event")
|
||||||
|
bridge.handleIMAPEvent(event)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Attempt to load users from the vault when triggered.
|
||||||
|
bridge.goLoad = bridge.tasks.Trigger(func(ctx context.Context) {
|
||||||
|
if err := bridge.loadUsers(ctx); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to load users")
|
||||||
|
if netErr := new(proton.NetError); !errors.As(err, &netErr) {
|
||||||
|
sentry.ReportError(bridge.reporter, "Failed to load users", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.publish(events.AllUsersLoaded{})
|
||||||
|
})
|
||||||
|
defer bridge.goLoad()
|
||||||
|
|
||||||
|
// Check for updates when triggered.
|
||||||
|
bridge.goUpdate = bridge.tasks.PeriodicOrTrigger(constants.UpdateCheckInterval, 0, func(ctx context.Context) {
|
||||||
|
logrus.Info("Checking for updates")
|
||||||
|
|
||||||
|
version, err := bridge.updater.GetVersionInfo(ctx, bridge.api, bridge.vault.GetUpdateChannel())
|
||||||
|
if err != nil {
|
||||||
|
bridge.publish(events.UpdateCheckFailed{Error: err})
|
||||||
|
} else {
|
||||||
|
bridge.handleUpdate(version)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
defer bridge.goUpdate()
|
||||||
|
|
||||||
|
// Install updates when available.
|
||||||
|
bridge.tasks.Once(func(ctx context.Context) {
|
||||||
|
async.RangeContext(ctx, bridge.installCh, func(job installJob) {
|
||||||
|
bridge.installUpdate(ctx, job)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEvents returns a channel of events of the given type.
|
||||||
|
// If no types are supplied, all events are returned.
|
||||||
|
func (bridge *Bridge) GetEvents(ofType ...events.Event) (<-chan events.Event, context.CancelFunc) {
|
||||||
|
watcher := bridge.addWatcher(ofType...)
|
||||||
|
|
||||||
|
return watcher.GetChannel(), func() { bridge.remWatcher(watcher) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) PushError(err error) {
|
||||||
|
bridge.errors = append(bridge.errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) GetErrors() []error {
|
||||||
|
return bridge.errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) Close(ctx context.Context) {
|
||||||
|
logrus.Info("Closing bridge")
|
||||||
|
|
||||||
|
// Stop heart beat before closing users.
|
||||||
|
bridge.heartbeat.stop()
|
||||||
|
|
||||||
|
// Close all users.
|
||||||
|
safe.Lock(func() {
|
||||||
|
for _, user := range bridge.users {
|
||||||
|
user.Close()
|
||||||
|
}
|
||||||
|
}, bridge.usersLock)
|
||||||
|
|
||||||
|
// Close the servers
|
||||||
|
if err := bridge.serverManager.CloseServers(ctx); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to close servers")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep in bridge and update in settings the last used version.
|
bridge.syncService.Close()
|
||||||
b.lastVersion = b.settings.Get(settings.LastVersionKey)
|
|
||||||
b.settings.Set(settings.LastVersionKey, constants.Version)
|
|
||||||
|
|
||||||
go b.heartbeat()
|
// Stop all ongoing tasks.
|
||||||
|
bridge.tasks.CancelAndWait()
|
||||||
|
|
||||||
|
// Close the focus service.
|
||||||
|
bridge.focusService.Close()
|
||||||
|
|
||||||
|
// Close the watchers.
|
||||||
|
bridge.watchersLock.Lock()
|
||||||
|
defer bridge.watchersLock.Unlock()
|
||||||
|
|
||||||
|
for _, watcher := range bridge.watchers {
|
||||||
|
watcher.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.watchers = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) publish(event events.Event) {
|
||||||
|
bridge.watchersLock.RLock()
|
||||||
|
defer bridge.watchersLock.RUnlock()
|
||||||
|
|
||||||
|
logrus.WithField("event", event).Debug("Publishing event")
|
||||||
|
|
||||||
|
for _, watcher := range bridge.watchers {
|
||||||
|
if watcher.IsWatching(event) {
|
||||||
|
if ok := watcher.Send(event); !ok {
|
||||||
|
logrus.WithField("event", event).Warn("Failed to send event to watcher")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) addWatcher(ofType ...events.Event) *watcher.Watcher[events.Event] {
|
||||||
|
bridge.watchersLock.Lock()
|
||||||
|
defer bridge.watchersLock.Unlock()
|
||||||
|
|
||||||
|
watcher := watcher.New(bridge.panicHandler, ofType...)
|
||||||
|
|
||||||
|
bridge.watchers = append(bridge.watchers, watcher)
|
||||||
|
|
||||||
|
return watcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) remWatcher(watcher *watcher.Watcher[events.Event]) {
|
||||||
|
bridge.watchersLock.Lock()
|
||||||
|
defer bridge.watchersLock.Unlock()
|
||||||
|
|
||||||
|
idx := xslices.Index(bridge.watchers, watcher)
|
||||||
|
|
||||||
|
if idx < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.watchers = append(bridge.watchers[:idx], bridge.watchers[idx+1:]...)
|
||||||
|
|
||||||
|
watcher.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) onStatusUp(_ context.Context) {
|
||||||
|
logrus.Info("Handling API status up")
|
||||||
|
|
||||||
|
bridge.goLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) onStatusDown(ctx context.Context) {
|
||||||
|
logrus.Info("Handling API status down")
|
||||||
|
|
||||||
|
for backoff := time.Second; ; backoff = min(backoff*2, 30*time.Second) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-time.After(backoff):
|
||||||
|
logrus.Info("Pinging API")
|
||||||
|
|
||||||
|
if err := bridge.api.Ping(ctx); err != nil {
|
||||||
|
logrus.WithError(err).Warn("Ping failed, API is still unreachable")
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
|
||||||
|
cert, err := tls.X509KeyPair(vault.GetBridgeTLSCert())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(a, b time.Duration) time.Duration {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// heartbeat sends a heartbeat signal once a day.
|
|
||||||
func (b *Bridge) heartbeat() {
|
|
||||||
for range time.Tick(time.Minute) {
|
|
||||||
lastHeartbeatDay, err := strconv.ParseInt(b.settings.Get(settings.LastHeartbeatKey), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're still on the same day, don't send a heartbeat.
|
|
||||||
if time.Now().YearDay() == int(lastHeartbeatDay) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're on the next (or a different) day, so send a heartbeat.
|
|
||||||
if err := b.SendMetric(metrics.New(metrics.Heartbeat, metrics.Daily, metrics.NoLabel)); err != nil {
|
|
||||||
logrus.WithError(err).Error("Failed to send heartbeat")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Heartbeat was sent successfully so update the last heartbeat day.
|
|
||||||
b.settings.Set(settings.LastHeartbeatKey, fmt.Sprintf("%v", time.Now().YearDay()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUpdateChannel returns currently set update channel.
|
|
||||||
func (b *Bridge) GetUpdateChannel() updater.UpdateChannel {
|
|
||||||
return updater.UpdateChannel(b.settings.Get(settings.UpdateChannelKey))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUpdateChannel switches update channel.
|
|
||||||
func (b *Bridge) SetUpdateChannel(channel updater.UpdateChannel) {
|
|
||||||
b.settings.Set(settings.UpdateChannelKey, string(channel))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) resetToLatestStable() error {
|
|
||||||
version, err := b.updater.Check()
|
|
||||||
if err != nil {
|
|
||||||
// If we can not check for updates - just remove all local updates and reset to base installer version.
|
|
||||||
// Not using `b.locations.ClearUpdates()` because `versioner.RemoveOtherVersions` can also handle
|
|
||||||
// case when it is needed to remove currently running verion.
|
|
||||||
if err := b.versioner.RemoveOtherVersions(semver.MustParse("0.0.0")); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to clear updates while downgrading channel")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If current version is same as upstream stable version - do nothing.
|
|
||||||
if version.Version.Equal(semver.MustParse(constants.Version)) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.updater.InstallUpdate(version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.versioner.RemoveOtherVersions(version.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FactoryReset will remove all local cache and settings.
|
|
||||||
// It will also downgrade to latest stable version if user is on early version.
|
|
||||||
func (b *Bridge) FactoryReset() {
|
|
||||||
wasEarly := b.GetUpdateChannel() == updater.EarlyChannel
|
|
||||||
|
|
||||||
b.settings.Set(settings.UpdateChannelKey, string(updater.StableChannel))
|
|
||||||
|
|
||||||
if wasEarly {
|
|
||||||
if err := b.resetToLatestStable(); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to reset to latest stable version")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.Users.ClearData(); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to remove bridge data")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.Users.ClearUsers(); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to remove bridge users")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKeychainApp returns current keychain helper.
|
|
||||||
func (b *Bridge) GetKeychainApp() string {
|
|
||||||
return b.settings.Get(settings.PreferredKeychainKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKeychainApp sets current keychain helper.
|
|
||||||
func (b *Bridge) SetKeychainApp(helper string) {
|
|
||||||
b.settings.Set(settings.PreferredKeychainKey, helper)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) EnableCache() error {
|
|
||||||
if err := b.Users.EnableCache(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.settings.SetBool(settings.CacheEnabledKey, true)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) DisableCache() error {
|
|
||||||
if err := b.Users.DisableCache(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.settings.SetBool(settings.CacheEnabledKey, false)
|
|
||||||
// Reset back to the default location when disabling.
|
|
||||||
b.settings.Set(settings.CacheLocationKey, b.cacheProvider.GetDefaultMessageCacheDir())
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) MigrateCache(from, to string) error {
|
|
||||||
if err := b.Users.MigrateCache(from, to); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.settings.Set(settings.CacheLocationKey, to)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetProxyAllowed instructs the app whether to use DoH to access an API proxy if necessary.
|
|
||||||
// It also needs to work before the app is initialised (because we may need to use the proxy at startup).
|
|
||||||
func (b *Bridge) SetProxyAllowed(proxyAllowed bool) {
|
|
||||||
b.settings.SetBool(settings.AllowProxyKey, proxyAllowed)
|
|
||||||
if proxyAllowed {
|
|
||||||
b.clientManager.AllowProxy()
|
|
||||||
} else {
|
|
||||||
b.clientManager.DisallowProxy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProxyAllowed returns whether use of DoH is enabled to access an API proxy if necessary.
|
|
||||||
func (b *Bridge) GetProxyAllowed() bool {
|
|
||||||
return b.settings.GetBool(settings.AllowProxyKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddError add an error to a global error list if it does not contain it yet. Adding nil is noop.
|
|
||||||
func (b *Bridge) AddError(err error) {
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if b.HasError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b.errors = append(b.errors, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DelError removes an error from global error list.
|
|
||||||
func (b *Bridge) DelError(err error) {
|
|
||||||
for idx, val := range b.errors {
|
|
||||||
if val == err {
|
|
||||||
b.errors = append(b.errors[:idx], b.errors[idx+1:]...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasError returnes true if global error list contains an err.
|
|
||||||
func (b *Bridge) HasError(err error) bool {
|
|
||||||
for _, val := range b.errors {
|
|
||||||
if val == err {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastVersion returns the version which was used in previous execution of
|
|
||||||
// Bridge.
|
|
||||||
func (b *Bridge) GetLastVersion() string {
|
|
||||||
return b.lastVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFirstStart returns true when Bridge is running for first time or after
|
|
||||||
// factory reset.
|
|
||||||
func (b *Bridge) IsFirstStart() bool {
|
|
||||||
return b.isFirstStart
|
|
||||||
}
|
|
||||||
|
|||||||
1133
internal/bridge/bridge_test.go
Normal file
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
// Copyright (c) 2023 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -18,188 +18,149 @@
|
|||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/logging"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
)
|
)
|
||||||
|
|
||||||
const MaxAttachmentSize = 7 * 1024 * 1024 // 7 MB total limit
|
const (
|
||||||
const MaxCompressedFilesCount = 6
|
DefaultMaxBugReportZipSize = 7 * 1024 * 1024
|
||||||
|
DefaultMaxSessionCountForBugReport = 10
|
||||||
|
)
|
||||||
|
|
||||||
var ErrSizeTooLarge = errors.New("file is too big")
|
type ReportBugReq struct {
|
||||||
|
OSType string
|
||||||
|
OSVersion string
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Username string
|
||||||
|
Email string
|
||||||
|
EmailClient string
|
||||||
|
IncludeLogs bool
|
||||||
|
}
|
||||||
|
|
||||||
// ReportBug reports a new bug from the user.
|
func (bridge *Bridge) ReportBug(ctx context.Context, report *ReportBugReq) error {
|
||||||
func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string, attachLogs bool) error {
|
if info, err := bridge.QueryUserInfo(report.Username); err == nil {
|
||||||
if user, err := b.GetUser(address); err == nil {
|
report.Username = info.Username
|
||||||
accountName = user.Username()
|
} else if userIDs := bridge.GetUserIDs(); len(userIDs) > 0 {
|
||||||
} else if users := b.GetUsers(); len(users) > 0 {
|
if err := bridge.vault.GetUser(userIDs[0], func(user *vault.User) {
|
||||||
accountName = users[0].Username()
|
report.Username = user.Username()
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
report := pmapi.ReportBugReq{
|
var attachments []proton.ReportBugAttachment
|
||||||
OS: osType,
|
if report.IncludeLogs {
|
||||||
OSVersion: osVersion,
|
logs, err := bridge.CollectLogs()
|
||||||
Browser: emailClient,
|
|
||||||
Title: "[Bridge] Bug",
|
|
||||||
Description: description,
|
|
||||||
Username: accountName,
|
|
||||||
Email: address,
|
|
||||||
}
|
|
||||||
|
|
||||||
if attachLogs {
|
|
||||||
logs, err := b.getMatchingLogs(
|
|
||||||
func(filename string) bool {
|
|
||||||
return logging.MatchLogName(filename) && !logging.MatchStackTraceName(filename)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("Can't get log files list")
|
return err
|
||||||
}
|
}
|
||||||
crashes, err := b.getMatchingLogs(
|
attachments = append(attachments, logs)
|
||||||
func(filename string) bool {
|
}
|
||||||
return logging.MatchLogName(filename) && logging.MatchStackTraceName(filename)
|
|
||||||
},
|
var firstAtt proton.ReportBugAttachment
|
||||||
)
|
if len(attachments) > 0 && report.IncludeLogs {
|
||||||
|
firstAtt = attachments[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
attachmentType := proton.AttachmentTypeSync
|
||||||
|
if len(attachments) > 1 {
|
||||||
|
attachmentType = proton.AttachmentTypeAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := bridge.createTicket(ctx, report, attachmentType, firstAtt)
|
||||||
|
if err != nil || token == "" {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
safe.RLock(func() {
|
||||||
|
for _, user := range bridge.users {
|
||||||
|
user.ReportBugSent()
|
||||||
|
}
|
||||||
|
}, bridge.usersLock)
|
||||||
|
|
||||||
|
// if we have a token we can append more attachment to the bugReport
|
||||||
|
for i, att := range attachments {
|
||||||
|
if i == 0 && report.IncludeLogs {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := bridge.appendComment(ctx, token, att)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("Can't get crash files list")
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
var matchFiles []string
|
|
||||||
|
|
||||||
matchFiles = append(matchFiles, logs[max(0, len(logs)-(MaxCompressedFilesCount/2)):]...)
|
|
||||||
matchFiles = append(matchFiles, crashes[max(0, len(crashes)-(MaxCompressedFilesCount/2)):]...)
|
|
||||||
|
|
||||||
archive, err := zipFiles(matchFiles)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Can't zip logs and crashes")
|
|
||||||
}
|
|
||||||
|
|
||||||
if archive != nil {
|
|
||||||
report.AddAttachment("logs.zip", "application/zip", archive)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.clientManager.ReportBug(context.Background(), report)
|
|
||||||
}
|
|
||||||
|
|
||||||
func max(a, b int) int {
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) getMatchingLogs(filenameMatchFunc func(string) bool) (filenames []string, err error) {
|
|
||||||
logsPath, err := b.locations.ProvideLogsPath()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(logsPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var matchFiles []string
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
if filenameMatchFunc(file.Name()) {
|
|
||||||
matchFiles = append(matchFiles, filepath.Join(logsPath, file.Name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(matchFiles) // Sorted by timestamp: oldest first.
|
|
||||||
|
|
||||||
return matchFiles, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type LimitedBuffer struct {
|
|
||||||
capacity int
|
|
||||||
buf *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLimitedBuffer(capacity int) *LimitedBuffer {
|
|
||||||
return &LimitedBuffer{
|
|
||||||
capacity: capacity,
|
|
||||||
buf: bytes.NewBuffer(make([]byte, 0, capacity)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *LimitedBuffer) Write(p []byte) (n int, err error) {
|
|
||||||
if len(p)+b.buf.Len() > b.capacity {
|
|
||||||
return 0, ErrSizeTooLarge
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.buf.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *LimitedBuffer) Read(p []byte) (n int, err error) {
|
|
||||||
return b.buf.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func zipFiles(filenames []string) (io.Reader, error) {
|
|
||||||
if len(filenames) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := NewLimitedBuffer(MaxAttachmentSize)
|
|
||||||
|
|
||||||
w := zip.NewWriter(buf)
|
|
||||||
defer w.Close() //nolint:errcheck
|
|
||||||
|
|
||||||
for _, file := range filenames {
|
|
||||||
err := addFileToZip(file, w)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.Close(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addFileToZip(filename string, writer *zip.Writer) error {
|
|
||||||
fileReader, err := os.Open(filepath.Clean(filename))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fileReader.Close() //nolint:errcheck,gosec
|
|
||||||
|
|
||||||
fileInfo, err := fileReader.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
header, err := zip.FileInfoHeader(fileInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
header.Method = zip.Deflate
|
|
||||||
header.Name = filepath.Base(filename)
|
|
||||||
|
|
||||||
fileWriter, err := writer.CreateHeader(header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(fileWriter, fileReader)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fileReader.Close()
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) CollectLogs() (proton.ReportBugAttachment, error) {
|
||||||
|
logsPath, err := bridge.locator.ProvideLogsPath()
|
||||||
|
if err != nil {
|
||||||
|
return proton.ReportBugAttachment{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer, err := logging.ZipLogsForBugReport(logsPath, DefaultMaxSessionCountForBugReport, DefaultMaxBugReportZipSize)
|
||||||
|
if err != nil {
|
||||||
|
return proton.ReportBugAttachment{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return proton.ReportBugAttachment{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return proton.ReportBugAttachment{
|
||||||
|
Name: "logs.zip",
|
||||||
|
Filename: "logs.zip",
|
||||||
|
MIMEType: "application/zip",
|
||||||
|
Body: body,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) createTicket(ctx context.Context, report *ReportBugReq,
|
||||||
|
asyncAttach proton.AttachmentType, att proton.ReportBugAttachment) (string, error) {
|
||||||
|
var attachments []proton.ReportBugAttachment
|
||||||
|
attachments = append(attachments, att)
|
||||||
|
res, err := bridge.api.ReportBug(ctx, proton.ReportBugReq{
|
||||||
|
OS: report.OSType,
|
||||||
|
OSVersion: report.OSVersion,
|
||||||
|
|
||||||
|
Title: "[Bridge] Bug - " + report.Title,
|
||||||
|
Description: report.Description,
|
||||||
|
|
||||||
|
Client: report.EmailClient,
|
||||||
|
ClientType: proton.ClientTypeEmail,
|
||||||
|
ClientVersion: constants.AppVersion(bridge.curVersion.Original()),
|
||||||
|
|
||||||
|
Username: report.Username,
|
||||||
|
Email: report.Email,
|
||||||
|
|
||||||
|
AsyncAttachments: asyncAttach,
|
||||||
|
}, attachments...)
|
||||||
|
|
||||||
|
if err != nil || asyncAttach != proton.AttachmentTypeAsync {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if asyncAttach == proton.AttachmentTypeAsync && res.Token == nil {
|
||||||
|
return "", errors.New("no token returns for AsyncAttachments")
|
||||||
|
}
|
||||||
|
|
||||||
|
return *res.Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) appendComment(ctx context.Context, token string, att proton.ReportBugAttachment) error {
|
||||||
|
var attachments []proton.ReportBugAttachment
|
||||||
|
attachments = append(attachments, att)
|
||||||
|
return bridge.api.ReportBugAttachement(ctx, proton.ReportBugAttachmentReq{
|
||||||
|
Product: proton.ClientTypeEmail,
|
||||||
|
Body: "Comment adding attachment: " + att.Filename,
|
||||||
|
Token: token,
|
||||||
|
}, attachments...)
|
||||||
|
}
|
||||||
|
|||||||
46
internal/bridge/config_status.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (bridge *Bridge) ReportBugClicked() {
|
||||||
|
safe.RLock(func() {
|
||||||
|
for _, user := range bridge.users {
|
||||||
|
user.ReportBugClicked()
|
||||||
|
}
|
||||||
|
}, bridge.usersLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) AutoconfigUsed(client string) {
|
||||||
|
safe.RLock(func() {
|
||||||
|
for _, user := range bridge.users {
|
||||||
|
user.AutoconfigUsed(client)
|
||||||
|
}
|
||||||
|
}, bridge.usersLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) ExternalLinkClicked(article string) {
|
||||||
|
safe.RLock(func() {
|
||||||
|
for _, user := range bridge.users {
|
||||||
|
user.ExternalLinkClicked(article)
|
||||||
|
}
|
||||||
|
}, bridge.usersLock)
|
||||||
|
}
|
||||||
90
internal/bridge/configure.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/clientconfig"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigureAppleMail configures Apple Mail for the given userID and address.
|
||||||
|
// If configuring Apple Mail for Catalina or newer, it ensures Bridge is using SSL.
|
||||||
|
func (bridge *Bridge) ConfigureAppleMail(ctx context.Context, userID, address string) error {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"userID": userID,
|
||||||
|
"address": logging.Sensitive(address),
|
||||||
|
}).Info("Configuring Apple Mail")
|
||||||
|
|
||||||
|
return safe.RLockRet(func() error {
|
||||||
|
user, ok := bridge.users[userID]
|
||||||
|
if !ok {
|
||||||
|
return ErrNoSuchUser
|
||||||
|
}
|
||||||
|
|
||||||
|
emails := user.Emails()
|
||||||
|
displayNames := user.DisplayNames()
|
||||||
|
if (len(emails) == 0) || (len(displayNames) == 0) {
|
||||||
|
return errors.New("could not retrieve user address info")
|
||||||
|
}
|
||||||
|
|
||||||
|
if address == "" {
|
||||||
|
address = emails[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var username, displayName, addresses string
|
||||||
|
if user.GetAddressMode() == vault.CombinedMode {
|
||||||
|
username = address
|
||||||
|
displayName = displayNames[username]
|
||||||
|
addresses = strings.Join(emails, ",")
|
||||||
|
} else {
|
||||||
|
username = address
|
||||||
|
addresses = address
|
||||||
|
displayName = displayNames[address]
|
||||||
|
if len(displayName) == 0 {
|
||||||
|
displayName = address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if useragent.IsCatalinaOrNewer() && !bridge.vault.GetSMTPSSL() {
|
||||||
|
if err := bridge.SetSMTPSSL(ctx, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (&clientconfig.AppleMail{}).Configure(
|
||||||
|
constants.Host,
|
||||||
|
bridge.vault.GetIMAPPort(),
|
||||||
|
bridge.vault.GetSMTPPort(),
|
||||||
|
bridge.vault.GetIMAPSSL(),
|
||||||
|
bridge.vault.GetSMTPSSL(),
|
||||||
|
username,
|
||||||
|
displayName,
|
||||||
|
addresses,
|
||||||
|
user.BridgePass(),
|
||||||
|
)
|
||||||
|
}, bridge.usersLock)
|
||||||
|
}
|
||||||
297
internal/bridge/debug.go
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gluon/imap"
|
||||||
|
"github.com/ProtonMail/gluon/rfc822"
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||||
|
"github.com/bradenaw/juniper/iterator"
|
||||||
|
"github.com/bradenaw/juniper/xslices"
|
||||||
|
goimap "github.com/emersion/go-imap"
|
||||||
|
goimapclient "github.com/emersion/go-imap/client"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckClientStateResult struct {
|
||||||
|
MissingMessages map[string]map[string]user.DiagMailboxMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CheckClientStateResult) AddMissingMessage(userID string, message user.DiagMailboxMessage) {
|
||||||
|
v, ok := c.MissingMessages[userID]
|
||||||
|
if !ok {
|
||||||
|
c.MissingMessages[userID] = map[string]user.DiagMailboxMessage{message.ID: message}
|
||||||
|
} else {
|
||||||
|
v[message.ID] = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckClientState checks the current IMAP client reported state against the proton server state and reports
|
||||||
|
// anything that is out of place.
|
||||||
|
func (bridge *Bridge) CheckClientState(ctx context.Context, checkFlags bool, progressCB func(string)) (CheckClientStateResult, error) {
|
||||||
|
bridge.usersLock.RLock()
|
||||||
|
defer bridge.usersLock.RUnlock()
|
||||||
|
|
||||||
|
users := maps.Values(bridge.users)
|
||||||
|
|
||||||
|
result := CheckClientStateResult{
|
||||||
|
MissingMessages: make(map[string]map[string]user.DiagMailboxMessage),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, usr := range users {
|
||||||
|
if progressCB != nil {
|
||||||
|
progressCB(fmt.Sprintf("Checking state for user %v", usr.Name()))
|
||||||
|
}
|
||||||
|
log := logrus.WithField("user", usr.Name()).WithField("diag", "state-check")
|
||||||
|
log.Debug("Retrieving all server metadata")
|
||||||
|
meta, err := usr.GetDiagnosticMetadata(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
success := true
|
||||||
|
|
||||||
|
if len(meta.Metadata) != len(meta.MessageIDs) {
|
||||||
|
log.Errorf("Metadata (%v) and message(%v) list sizes do not match", len(meta.Metadata), len(meta.MessageIDs))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Building state")
|
||||||
|
state, err := meta.BuildMailboxToMessageMap(ctx, usr)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Failed to build state")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := bridge.GetUserInfo(usr.ID())
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Failed to get user info")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := fmt.Sprintf("127.0.0.1:%v", bridge.GetIMAPPort())
|
||||||
|
|
||||||
|
for account, mboxMap := range state {
|
||||||
|
if progressCB != nil {
|
||||||
|
progressCB(fmt.Sprintf("Checking state for user %v's account '%v'", usr.Name(), account))
|
||||||
|
}
|
||||||
|
if err := func(account string, mboxMap user.AccountMailboxMap) error {
|
||||||
|
client, err := goimapclient.Dial(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Failed to connect to imap client")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = client.Logout()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := client.Login(account, string(info.BridgePass)); err != nil {
|
||||||
|
return fmt.Errorf("failed to login for user %v:%w", usr.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := log.WithField("account", account)
|
||||||
|
for mboxName, messageList := range mboxMap {
|
||||||
|
log := log.WithField("mbox", mboxName)
|
||||||
|
status, err := client.Select(mboxName, true)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("Failed to select mailbox %v", messageList)
|
||||||
|
return fmt.Errorf("failed to select '%v':%w", mboxName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Checking message count")
|
||||||
|
|
||||||
|
if int(status.Messages) != len(messageList) {
|
||||||
|
success = false
|
||||||
|
log.Errorf("Message count doesn't match, got '%v' expected '%v'", status.Messages, len(messageList))
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, err := clientGetMessageIDs(client, mboxName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get message ids for mbox '%v': %w", mboxName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range messageList {
|
||||||
|
imapFlags, ok := ids[msg.ID]
|
||||||
|
if !ok {
|
||||||
|
if meta.FailedMessageIDs.Contains(msg.ID) {
|
||||||
|
log.Warningf("Missing message '%v', but it is part of failed message set", msg.ID)
|
||||||
|
} else {
|
||||||
|
log.Errorf("Missing message '%v'", msg.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.AddMissingMessage(msg.UserID, msg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkFlags {
|
||||||
|
if !imapFlags.Equals(msg.Flags) {
|
||||||
|
log.Errorf("Message '%v' flags do mot match, got=%v, expected=%v",
|
||||||
|
msg.ID,
|
||||||
|
imapFlags.ToSlice(),
|
||||||
|
msg.Flags.ToSlice(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
log.Errorf("State does not match")
|
||||||
|
} else {
|
||||||
|
log.Info("State matches")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}(account, mboxMap); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for orphaned messages (only present in All Mail)
|
||||||
|
if progressCB != nil {
|
||||||
|
progressCB(fmt.Sprintf("Checking user %v for orphans", usr.Name()))
|
||||||
|
}
|
||||||
|
log.Debugf("Checking for orphans")
|
||||||
|
|
||||||
|
for _, m := range meta.Metadata {
|
||||||
|
filteredLabels := xslices.Filter(m.LabelIDs, func(t string) bool {
|
||||||
|
switch t {
|
||||||
|
case proton.AllMailLabel:
|
||||||
|
return false
|
||||||
|
case proton.AllSentLabel:
|
||||||
|
return false
|
||||||
|
case proton.AllDraftsLabel:
|
||||||
|
return false
|
||||||
|
case proton.OutboxLabel:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(filteredLabels) == 0 {
|
||||||
|
log.Warnf("Message %v is only present in All Mail (Subject=%v)", m.ID, m.Subject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) DebugDownloadFailedMessages(
|
||||||
|
ctx context.Context,
|
||||||
|
result CheckClientStateResult,
|
||||||
|
exportPath string,
|
||||||
|
progressCB func(string, int, int),
|
||||||
|
) error {
|
||||||
|
bridge.usersLock.RLock()
|
||||||
|
defer bridge.usersLock.RUnlock()
|
||||||
|
|
||||||
|
for userID, messages := range result.MissingMessages {
|
||||||
|
usr, ok := bridge.users[userID]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to find user with id %v", userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
userDir := filepath.Join(exportPath, userID)
|
||||||
|
if err := os.MkdirAll(userDir, 0o700); err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory '%v': %w", userDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := usr.DebugDownloadMessages(ctx, userDir, messages, progressCB); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientGetMessageIDs(client *goimapclient.Client, mailbox string) (map[string]imap.FlagSet, error) {
|
||||||
|
status, err := client.Select(mailbox, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.Messages == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resCh := make(chan *goimap.Message)
|
||||||
|
|
||||||
|
section, err := goimap.ParseBodySectionName("BODY[HEADER]")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchItems := []goimap.FetchItem{"BODY[HEADER]", goimap.FetchFlags}
|
||||||
|
|
||||||
|
seq, err := goimap.ParseSeqSet("1:*")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := client.Fetch(
|
||||||
|
seq,
|
||||||
|
fetchItems,
|
||||||
|
resCh,
|
||||||
|
); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
messages := iterator.Collect(iterator.Chan(resCh))
|
||||||
|
|
||||||
|
ids := make(map[string]imap.FlagSet, len(messages))
|
||||||
|
|
||||||
|
for i, m := range messages {
|
||||||
|
literal, err := io.ReadAll(m.GetBody(section))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := rfc822.NewHeader(literal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse header for msg %v: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
internalID, ok := header.GetChecked("X-Pm-Internal-Id")
|
||||||
|
if !ok {
|
||||||
|
logrus.Errorf("Message %v does not have internal id", internalID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
messageFlags := imap.NewFlagSet(m.Flags...)
|
||||||
|
|
||||||
|
// Recent and Deleted are not part of the proton flag set.
|
||||||
|
messageFlags.RemoveFromSelf("\\Recent")
|
||||||
|
messageFlags.RemoveFromSelf("\\Deleted")
|
||||||
|
|
||||||
|
ids[internalID] = messageFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
175
internal/bridge/draft_test.go
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gluon/rfc822"
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
"github.com/ProtonMail/go-proton-api/server"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
go_imap "github.com/emersion/go-imap"
|
||||||
|
"github.com/emersion/go-sasl"
|
||||||
|
"github.com/emersion/go-smtp"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBridge_HandleDraftsSendFromOtherClient(t *testing.T) {
|
||||||
|
getGluonHeaderID := func(literal []byte) (string, string) {
|
||||||
|
h, err := rfc822.NewHeader(literal)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
gluonID, ok := h.GetChecked("X-Pm-Gluon-Id")
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
externalID, ok := h.GetChecked("Message-Id")
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
return gluonID, externalID
|
||||||
|
}
|
||||||
|
|
||||||
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
|
_, _, err := s.CreateUser("imap", password)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, _, err = s.CreateUser("bar", password)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// The initial user should be fully synced.
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
|
waiter := waitForIMAPServerReady(b)
|
||||||
|
defer waiter.Done()
|
||||||
|
|
||||||
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
userID, err := b.LoginFull(ctx, "imap", password, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, userID, (<-syncCh).UserID)
|
||||||
|
waiter.Wait()
|
||||||
|
|
||||||
|
info, err := b.GetUserInfo(userID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, info.State == bridge.Connected)
|
||||||
|
|
||||||
|
client, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
|
||||||
|
defer func() { _ = client.Logout() }()
|
||||||
|
|
||||||
|
// Create first draft in client.
|
||||||
|
literal := fmt.Sprintf(`From: %v
|
||||||
|
To: %v
|
||||||
|
Date: Fri, 3 Feb 2023 01:04:32 +0100
|
||||||
|
Subject: Foo
|
||||||
|
|
||||||
|
Hello
|
||||||
|
`, info.Addresses[0], "bar@proton.local")
|
||||||
|
|
||||||
|
require.NoError(t, client.Append("Drafts", nil, time.Now(), strings.NewReader(literal)))
|
||||||
|
// Verify the draft is available in client.
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
status, err := client.Status("Drafts", []go_imap.StatusItem{go_imap.StatusMessages})
|
||||||
|
require.NoError(t, err)
|
||||||
|
return status.Messages == 1
|
||||||
|
}, 2*time.Second, time.Second)
|
||||||
|
|
||||||
|
// Retrieve the new literal so we can have the Proton Message ID.
|
||||||
|
messages, err := clientFetch(client, "Drafts")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(messages))
|
||||||
|
|
||||||
|
newLiteral, err := io.ReadAll(messages[0].GetBody(must(go_imap.ParseBodySectionName("BODY[]"))))
|
||||||
|
require.NoError(t, err)
|
||||||
|
logrus.Info(string(newLiteral))
|
||||||
|
|
||||||
|
newLiteralID, newLiteralExternID := getGluonHeaderID(newLiteral)
|
||||||
|
|
||||||
|
// Modify new literal.
|
||||||
|
newLiteralModified := append(newLiteral, []byte(" world from client2")...) //nolint:gocritic
|
||||||
|
|
||||||
|
func() {
|
||||||
|
smtpClient, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(b.GetSMTPPort())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() { _ = smtpClient.Close() }()
|
||||||
|
|
||||||
|
// Upgrade to TLS.
|
||||||
|
require.NoError(t, smtpClient.StartTLS(&tls.Config{InsecureSkipVerify: true}))
|
||||||
|
|
||||||
|
// Authorize with SASL PLAIN.
|
||||||
|
require.NoError(t, smtpClient.Auth(sasl.NewPlainClient(
|
||||||
|
info.Addresses[0],
|
||||||
|
info.Addresses[0],
|
||||||
|
string(info.BridgePass)),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Send the message.
|
||||||
|
require.NoError(t, smtpClient.SendMail(
|
||||||
|
info.Addresses[0],
|
||||||
|
[]string{"bar@proton.local"},
|
||||||
|
bytes.NewReader(newLiteralModified),
|
||||||
|
))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Append message to Sent as the imap client would.
|
||||||
|
require.NoError(t, client.Append("Sent", nil, time.Now(), strings.NewReader(literal)))
|
||||||
|
|
||||||
|
// Verify the sent message gets updated with the new literal.
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
// Check if sent message matches the latest draft.
|
||||||
|
messagesClient1, err := clientFetch(client, "Sent", "BODY[TEXT]", "BODY[]")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if len(messagesClient1) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sentLiteral, err := io.ReadAll(messagesClient1[0].GetBody(must(go_imap.ParseBodySectionName("BODY[]"))))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
sentLiteralID, sentLiteralExternID := getGluonHeaderID(sentLiteral)
|
||||||
|
|
||||||
|
sentLiteralText, err := io.ReadAll(messagesClient1[0].GetBody(must(go_imap.ParseBodySectionName("BODY[TEXT]"))))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
sentLiteralStr := string(sentLiteralText)
|
||||||
|
|
||||||
|
literalMatches := sentLiteralStr == "Hello\r\n world from client2\r\n"
|
||||||
|
|
||||||
|
idIsDifferent := sentLiteralID != newLiteralID
|
||||||
|
|
||||||
|
externIDMatches := sentLiteralExternID == newLiteralExternID
|
||||||
|
|
||||||
|
return literalMatches && idIsDifferent && externIDMatches
|
||||||
|
}, 2*time.Second, time.Second)
|
||||||
|
})
|
||||||
|
}, server.WithMessageDedup())
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
// Copyright (c) 2023 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -13,13 +13,21 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package updater
|
package bridge
|
||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrDownloadVerify = errors.New("failed to download or verify the update")
|
ErrVaultInsecure = errors.New("the vault is insecure")
|
||||||
ErrInstall = errors.New("failed to install the update")
|
ErrVaultCorrupt = errors.New("the vault is corrupt")
|
||||||
|
ErrWatchUpdates = errors.New("failed to watch for updates")
|
||||||
|
|
||||||
|
ErrNoSuchUser = errors.New("no such user")
|
||||||
|
ErrUserAlreadyExists = errors.New("user already exists")
|
||||||
|
ErrUserAlreadyLoggedIn = errors.New("the user is already logged in")
|
||||||
|
ErrNotImplemented = errors.New("not implemented")
|
||||||
|
|
||||||
|
ErrSizeTooLarge = errors.New("file is too big")
|
||||||
)
|
)
|
||||||
45
internal/bridge/events.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gluon/watcher"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bridgeEventSubscription struct {
|
||||||
|
b *Bridge
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bridgeEventSubscription) Add(ofType ...events.Event) *watcher.Watcher[events.Event] {
|
||||||
|
return b.b.addWatcher(ofType...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bridgeEventSubscription) Remove(watcher *watcher.Watcher[events.Event]) {
|
||||||
|
b.b.remWatcher(watcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
type bridgeEventPublisher struct {
|
||||||
|
b *Bridge
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bridgeEventPublisher) PublishEvent(_ context.Context, event events.Event) {
|
||||||
|
b.b.publish(event)
|
||||||
|
}
|
||||||
167
internal/bridge/heartbeat.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gluon/async"
|
||||||
|
"github.com/ProtonMail/gluon/reporter"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const HeartbeatCheckInterval = time.Hour
|
||||||
|
|
||||||
|
type heartBeatState struct {
|
||||||
|
task *async.Group
|
||||||
|
telemetry.Heartbeat
|
||||||
|
taskLock sync.Mutex
|
||||||
|
taskStarted bool
|
||||||
|
taskInterval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHeartBeatState(ctx context.Context, panicHandler async.PanicHandler) *heartBeatState {
|
||||||
|
return &heartBeatState{
|
||||||
|
task: async.NewGroup(ctx, panicHandler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *heartBeatState) init(bridge *Bridge, manager telemetry.HeartbeatManager) {
|
||||||
|
h.Heartbeat = telemetry.NewHeartbeat(manager, 1143, 1025, bridge.GetGluonCacheDir(), bridge.keychains.GetDefaultHelper())
|
||||||
|
h.taskInterval = manager.GetHeartbeatPeriodicInterval()
|
||||||
|
h.SetRollout(bridge.GetUpdateRollout())
|
||||||
|
h.SetAutoStart(bridge.GetAutostart())
|
||||||
|
h.SetAutoUpdate(bridge.GetAutoUpdate())
|
||||||
|
h.SetBeta(bridge.GetUpdateChannel())
|
||||||
|
h.SetDoh(bridge.GetProxyAllowed())
|
||||||
|
h.SetShowAllMail(bridge.GetShowAllMail())
|
||||||
|
h.SetIMAPConnectionMode(bridge.GetIMAPSSL())
|
||||||
|
h.SetSMTPConnectionMode(bridge.GetSMTPSSL())
|
||||||
|
h.SetIMAPPort(bridge.GetIMAPPort())
|
||||||
|
h.SetSMTPPort(bridge.GetSMTPPort())
|
||||||
|
h.SetCacheLocation(bridge.GetGluonCacheDir())
|
||||||
|
if val, err := bridge.GetKeychainApp(); err != nil {
|
||||||
|
h.SetKeyChainPref(val)
|
||||||
|
} else {
|
||||||
|
h.SetKeyChainPref(bridge.keychains.GetDefaultHelper())
|
||||||
|
}
|
||||||
|
h.SetPrevVersion(bridge.GetLastVersion().String())
|
||||||
|
|
||||||
|
safe.RLock(func() {
|
||||||
|
var splitMode = false
|
||||||
|
for _, user := range bridge.users {
|
||||||
|
if user.GetAddressMode() == vault.SplitMode {
|
||||||
|
splitMode = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var nbAccount = len(bridge.users)
|
||||||
|
h.SetNbAccount(nbAccount)
|
||||||
|
h.SetSplitMode(splitMode)
|
||||||
|
|
||||||
|
// Do not try to send if there is no user yet.
|
||||||
|
if nbAccount > 0 {
|
||||||
|
defer h.start()
|
||||||
|
}
|
||||||
|
}, bridge.usersLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *heartBeatState) start() {
|
||||||
|
h.taskLock.Lock()
|
||||||
|
defer h.taskLock.Unlock()
|
||||||
|
if h.taskStarted {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.taskStarted = true
|
||||||
|
|
||||||
|
h.task.PeriodicOrTrigger(h.taskInterval, 0, func(ctx context.Context) {
|
||||||
|
logrus.Debug("Checking for heartbeat")
|
||||||
|
|
||||||
|
h.TrySending(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *heartBeatState) stop() {
|
||||||
|
h.taskLock.Lock()
|
||||||
|
defer h.taskLock.Unlock()
|
||||||
|
if !h.taskStarted {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.task.CancelAndWait()
|
||||||
|
h.taskStarted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) IsTelemetryAvailable(ctx context.Context) bool {
|
||||||
|
var flag = true
|
||||||
|
if bridge.GetTelemetryDisabled() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
safe.RLock(func() {
|
||||||
|
for _, user := range bridge.users {
|
||||||
|
flag = flag && user.IsTelemetryEnabled(ctx)
|
||||||
|
}
|
||||||
|
}, bridge.usersLock)
|
||||||
|
|
||||||
|
return flag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) SendHeartbeat(ctx context.Context, heartbeat *telemetry.HeartbeatData) bool {
|
||||||
|
data, err := json.Marshal(heartbeat)
|
||||||
|
if err != nil {
|
||||||
|
if err := bridge.reporter.ReportMessageWithContext("Cannot parse heartbeat data.", reporter.Context{
|
||||||
|
"error": err,
|
||||||
|
}); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to parse heartbeat data.")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var sent = false
|
||||||
|
|
||||||
|
safe.RLock(func() {
|
||||||
|
for _, user := range bridge.users {
|
||||||
|
if err := user.SendTelemetry(ctx, data); err == nil {
|
||||||
|
sent = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, bridge.usersLock)
|
||||||
|
|
||||||
|
return sent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) GetLastHeartbeatSent() time.Time {
|
||||||
|
return bridge.vault.GetLastHeartbeatSent()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) SetLastHeartbeatSent(timestamp time.Time) error {
|
||||||
|
return bridge.vault.SetLastHeartbeatSent(timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) GetHeartbeatPeriodicInterval() time.Duration {
|
||||||
|
return HeartbeatCheckInterval
|
||||||
|
}
|
||||||
74
internal/bridge/identifier.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
func (bridge *Bridge) GetCurrentUserAgent() string {
|
||||||
|
return bridge.identifier.GetUserAgent()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) SetCurrentPlatform(platform string) {
|
||||||
|
bridge.identifier.SetPlatform(platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) setUserAgent(name, version string) {
|
||||||
|
currentUserAgent := bridge.identifier.GetClientString()
|
||||||
|
|
||||||
|
bridge.identifier.SetClient(name, version)
|
||||||
|
|
||||||
|
newUserAgent := bridge.identifier.GetClientString()
|
||||||
|
|
||||||
|
if currentUserAgent != newUserAgent {
|
||||||
|
if err := bridge.vault.SetLastUserAgent(newUserAgent); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to write new user agent to vault")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type bridgeUserAgentUpdater struct {
|
||||||
|
*Bridge
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeUserAgentUpdater) GetUserAgent() string {
|
||||||
|
return b.identifier.GetUserAgent()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeUserAgentUpdater) HasClient() bool {
|
||||||
|
return b.identifier.HasClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeUserAgentUpdater) SetClient(name, version string) {
|
||||||
|
b.identifier.SetClient(name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeUserAgentUpdater) SetPlatform(platform string) {
|
||||||
|
b.identifier.SetPlatform(platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeUserAgentUpdater) SetClientString(client string) {
|
||||||
|
b.identifier.SetClientString(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeUserAgentUpdater) GetClientString() string {
|
||||||
|
return b.identifier.GetClientString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeUserAgentUpdater) SetUserAgent(name, version string) {
|
||||||
|
b.setUserAgent(name, version)
|
||||||
|
}
|
||||||
129
internal/bridge/imap.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
imapEvents "github.com/ProtonMail/gluon/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (bridge *Bridge) restartIMAP(ctx context.Context) error {
|
||||||
|
return bridge.serverManager.RestartIMAP(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
|
||||||
|
switch event := event.(type) {
|
||||||
|
case imapEvents.UserAdded:
|
||||||
|
for labelID, count := range event.Counts {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"gluonID": event.UserID,
|
||||||
|
"labelID": labelID,
|
||||||
|
"count": count,
|
||||||
|
}).Info("Received mailbox message count")
|
||||||
|
}
|
||||||
|
|
||||||
|
case imapEvents.IMAPID:
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"sessionID": event.SessionID,
|
||||||
|
"name": event.IMAPID.Name,
|
||||||
|
"version": event.IMAPID.Version,
|
||||||
|
}).Info("Received IMAP ID")
|
||||||
|
|
||||||
|
if event.IMAPID.Name != "" && event.IMAPID.Version != "" {
|
||||||
|
bridge.setUserAgent(event.IMAPID.Name, event.IMAPID.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
case imapEvents.LoginFailed:
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"sessionID": event.SessionID,
|
||||||
|
"username": event.Username,
|
||||||
|
"pkg": "imap",
|
||||||
|
}).Error("Incorrect login credentials.")
|
||||||
|
bridge.publish(events.IMAPLoginFailed{Username: event.Username})
|
||||||
|
|
||||||
|
case imapEvents.Login:
|
||||||
|
if strings.Contains(bridge.GetCurrentUserAgent(), useragent.DefaultUserAgent) {
|
||||||
|
bridge.setUserAgent(useragent.UnknownClient, useragent.DefaultVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type bridgeIMAPSettings struct {
|
||||||
|
b *Bridge
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) EventPublisher() imapsmtpserver.IMAPEventPublisher {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) TLSConfig() *tls.Config {
|
||||||
|
return b.b.tlsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) LogClient() bool {
|
||||||
|
return b.b.logIMAPClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) LogServer() bool {
|
||||||
|
return b.b.logIMAPServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) Port() int {
|
||||||
|
return b.b.vault.GetIMAPPort()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) SetPort(i int) error {
|
||||||
|
return b.b.vault.SetIMAPPort(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) UseSSL() bool {
|
||||||
|
return b.b.vault.GetIMAPSSL()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) CacheDirectory() string {
|
||||||
|
return b.b.GetGluonCacheDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) DataDirectory() (string, error) {
|
||||||
|
return b.b.GetGluonDataDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) SetCacheDirectory(s string) error {
|
||||||
|
return b.b.vault.SetGluonDir(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) Version() *semver.Version {
|
||||||
|
return b.b.curVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) PublishIMAPEvent(ctx context.Context, event imapEvents.Event) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case b.b.imapEventCh <- event:
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
26
internal/bridge/imapsmtp_telemetry.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
type bridgeIMAPSMTPTelemetry struct {
|
||||||
|
b *Bridge
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bridgeIMAPSMTPTelemetry) SetCacheLocation(s string) {
|
||||||
|
b.b.heartbeat.SetCacheLocation(s)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
// Copyright (c) 2023 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -13,13 +13,12 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package cache
|
package bridge
|
||||||
|
|
||||||
type Options struct {
|
import "golang.org/x/exp/maps"
|
||||||
MinFreeAbs uint64
|
|
||||||
MinFreeRat float64
|
func (bridge *Bridge) GetHelpersNames() []string {
|
||||||
ConcurrentRead int
|
return maps.Keys(bridge.keychains.GetHelpers())
|
||||||
ConcurrentWrite int
|
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
// Copyright (c) 2023 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -13,20 +13,18 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package store
|
package bridge
|
||||||
|
|
||||||
import "encoding/binary"
|
func (bridge *Bridge) GetLogsPath() (string, error) {
|
||||||
|
return bridge.locator.ProvideLogsPath()
|
||||||
// itob returns a 4-byte big endian representation of v.
|
|
||||||
func itob(v uint32) []byte {
|
|
||||||
b := make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(b, v)
|
|
||||||
return b
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// btoi returns the uint32 represented by b.
|
func (bridge *Bridge) GetLicenseFilePath() string {
|
||||||
func btoi(b []byte) uint32 {
|
return bridge.locator.GetLicenseFilePath()
|
||||||
return binary.BigEndian.Uint32(b)
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) GetDependencyLicensesLink() string {
|
||||||
|
return bridge.locator.GetDependencyLicensesLink()
|
||||||
}
|
}
|
||||||
36
internal/bridge/main_test.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"go.uber.org/goleak"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
if level := os.Getenv("BRIDGE_LOG_LEVEL"); level != "" {
|
||||||
|
if parsed, err := logrus.ParseLevel(level); err == nil {
|
||||||
|
logrus.SetLevel(parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
goleak.VerifyTestMain(m, goleak.IgnoreCurrent())
|
||||||
|
}
|
||||||
162
internal/bridge/mocks.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package bridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge/mocks"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mocks struct {
|
||||||
|
ProxyCtl *mocks.MockProxyController
|
||||||
|
TLSReporter *mocks.MockTLSReporter
|
||||||
|
TLSIssueCh chan struct{}
|
||||||
|
|
||||||
|
Updater *TestUpdater
|
||||||
|
Autostarter *mocks.MockAutostarter
|
||||||
|
|
||||||
|
CrashHandler *mocks.MockPanicHandler
|
||||||
|
Reporter *mocks.MockReporter
|
||||||
|
Heartbeat *mocks.MockHeartbeatManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMocks(tb testing.TB, version, minAuto *semver.Version) *Mocks {
|
||||||
|
ctl := gomock.NewController(tb)
|
||||||
|
|
||||||
|
mocks := &Mocks{
|
||||||
|
ProxyCtl: mocks.NewMockProxyController(ctl),
|
||||||
|
TLSReporter: mocks.NewMockTLSReporter(ctl),
|
||||||
|
TLSIssueCh: make(chan struct{}),
|
||||||
|
|
||||||
|
Updater: NewTestUpdater(version, minAuto),
|
||||||
|
Autostarter: mocks.NewMockAutostarter(ctl),
|
||||||
|
|
||||||
|
CrashHandler: mocks.NewMockPanicHandler(ctl),
|
||||||
|
Reporter: mocks.NewMockReporter(ctl),
|
||||||
|
Heartbeat: mocks.NewMockHeartbeatManager(ctl),
|
||||||
|
}
|
||||||
|
|
||||||
|
// When getting the TLS issue channel, we want to return the test channel.
|
||||||
|
mocks.TLSReporter.EXPECT().GetTLSIssueCh().Return(mocks.TLSIssueCh).AnyTimes()
|
||||||
|
|
||||||
|
// This is called at the end of any go-routine:
|
||||||
|
mocks.CrashHandler.EXPECT().HandlePanic(gomock.Any()).AnyTimes()
|
||||||
|
|
||||||
|
// this is called at start of heartbeat process.
|
||||||
|
mocks.Heartbeat.EXPECT().IsTelemetryAvailable(gomock.Any()).AnyTimes()
|
||||||
|
mocks.Heartbeat.EXPECT().GetHeartbeatPeriodicInterval().AnyTimes().Return(500 * time.Millisecond)
|
||||||
|
|
||||||
|
return mocks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mocks *Mocks) Close() {
|
||||||
|
close(mocks.TLSIssueCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestCookieJar struct {
|
||||||
|
cookies map[string][]*http.Cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestCookieJar() *TestCookieJar {
|
||||||
|
return &TestCookieJar{
|
||||||
|
cookies: make(map[string][]*http.Cookie),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *TestCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||||
|
j.cookies[u.Host] = cookies
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *TestCookieJar) Cookies(u *url.URL) []*http.Cookie {
|
||||||
|
return j.cookies[u.Host]
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestLocationsProvider struct {
|
||||||
|
config, data, cache string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestLocationsProvider(dir string) *TestLocationsProvider {
|
||||||
|
config, err := os.MkdirTemp(dir, "config")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.MkdirTemp(dir, "data")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cache, err := os.MkdirTemp(dir, "cache")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TestLocationsProvider{
|
||||||
|
config: config,
|
||||||
|
data: data,
|
||||||
|
cache: cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *TestLocationsProvider) UserConfig() string {
|
||||||
|
return provider.config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *TestLocationsProvider) UserData() string {
|
||||||
|
return provider.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *TestLocationsProvider) UserCache() string {
|
||||||
|
return provider.cache
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestUpdater struct {
|
||||||
|
latest updater.VersionInfo
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestUpdater(version, minAuto *semver.Version) *TestUpdater {
|
||||||
|
return &TestUpdater{
|
||||||
|
latest: updater.VersionInfo{
|
||||||
|
Version: version,
|
||||||
|
MinAuto: minAuto,
|
||||||
|
|
||||||
|
RolloutProportion: 1.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testUpdater *TestUpdater) SetLatestVersion(version, minAuto *semver.Version) {
|
||||||
|
testUpdater.lock.Lock()
|
||||||
|
defer testUpdater.lock.Unlock()
|
||||||
|
|
||||||
|
testUpdater.latest = updater.VersionInfo{
|
||||||
|
Version: version,
|
||||||
|
MinAuto: minAuto,
|
||||||
|
|
||||||
|
RolloutProportion: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testUpdater *TestUpdater) GetVersionInfo(_ context.Context, _ updater.Downloader, _ updater.Channel) (updater.VersionInfo, error) {
|
||||||
|
testUpdater.lock.RLock()
|
||||||
|
defer testUpdater.lock.RUnlock()
|
||||||
|
|
||||||
|
return testUpdater.latest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testUpdater *TestUpdater) InstallUpdate(_ context.Context, _ updater.Downloader, _ updater.VersionInfo) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (testUpdater *TestUpdater) RemoveOldUpdates() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
46
internal/bridge/mocks/async_mocks.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/ProtonMail/gluon/async (interfaces: PanicHandler)
|
||||||
|
|
||||||
|
// Package mocks is a generated GoMock package.
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockPanicHandler is a mock of PanicHandler interface.
|
||||||
|
type MockPanicHandler struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockPanicHandlerMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler.
|
||||||
|
type MockPanicHandlerMockRecorder struct {
|
||||||
|
mock *MockPanicHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockPanicHandler creates a new mock instance.
|
||||||
|
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
|
||||||
|
mock := &MockPanicHandler{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockPanicHandlerMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlePanic mocks base method.
|
||||||
|
func (m *MockPanicHandler) HandlePanic(arg0 interface{}) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "HandlePanic", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlePanic indicates an expected call of HandlePanic.
|
||||||
|
func (mr *MockPanicHandlerMockRecorder) HandlePanic(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic), arg0)
|
||||||
|
}
|
||||||
90
internal/bridge/mocks/gluon_mocks.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/ProtonMail/gluon/reporter (interfaces: Reporter)
|
||||||
|
|
||||||
|
// Package mocks is a generated GoMock package.
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockReporter is a mock of Reporter interface.
|
||||||
|
type MockReporter struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockReporterMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockReporterMockRecorder is the mock recorder for MockReporter.
|
||||||
|
type MockReporterMockRecorder struct {
|
||||||
|
mock *MockReporter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockReporter creates a new mock instance.
|
||||||
|
func NewMockReporter(ctrl *gomock.Controller) *MockReporter {
|
||||||
|
mock := &MockReporter{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockReporterMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockReporter) EXPECT() *MockReporterMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportException mocks base method.
|
||||||
|
func (m *MockReporter) ReportException(arg0 interface{}) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ReportException", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportException indicates an expected call of ReportException.
|
||||||
|
func (mr *MockReporterMockRecorder) ReportException(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportException", reflect.TypeOf((*MockReporter)(nil).ReportException), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportExceptionWithContext mocks base method.
|
||||||
|
func (m *MockReporter) ReportExceptionWithContext(arg0 interface{}, arg1 map[string]interface{}) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ReportExceptionWithContext", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportExceptionWithContext indicates an expected call of ReportExceptionWithContext.
|
||||||
|
func (mr *MockReporterMockRecorder) ReportExceptionWithContext(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportExceptionWithContext", reflect.TypeOf((*MockReporter)(nil).ReportExceptionWithContext), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportMessage mocks base method.
|
||||||
|
func (m *MockReporter) ReportMessage(arg0 string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ReportMessage", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportMessage indicates an expected call of ReportMessage.
|
||||||
|
func (mr *MockReporterMockRecorder) ReportMessage(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportMessage", reflect.TypeOf((*MockReporter)(nil).ReportMessage), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportMessageWithContext mocks base method.
|
||||||
|
func (m *MockReporter) ReportMessageWithContext(arg0 string, arg1 map[string]interface{}) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ReportMessageWithContext", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportMessageWithContext indicates an expected call of ReportMessageWithContext.
|
||||||
|
func (mr *MockReporterMockRecorder) ReportMessageWithContext(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportMessageWithContext", reflect.TypeOf((*MockReporter)(nil).ReportMessageWithContext), arg0, arg1)
|
||||||
|
}
|
||||||
108
internal/bridge/mocks/matcher.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type refreshContextMatcher struct {
|
||||||
|
wantRefresh proton.RefreshFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRefreshContextMatcher(refreshFlag proton.RefreshFlag) *refreshContextMatcher { //nolint:revive
|
||||||
|
return &refreshContextMatcher{wantRefresh: refreshFlag}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *refreshContextMatcher) Matches(x interface{}) bool {
|
||||||
|
context, ok := x.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
i, ok := context["EventLoop"]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
el, ok := i.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
vID, ok := el["EventID"]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
id, ok := vID.(string)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if id == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
vRefresh, ok := el["Refresh"]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh, ok := vRefresh.(proton.RefreshFlag)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return refresh == m.wantRefresh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *refreshContextMatcher) String() string {
|
||||||
|
return `map[string]interface which contains "Refresh" field with value proton.RefreshAll`
|
||||||
|
}
|
||||||
|
|
||||||
|
type closedConnectionMatcher struct{}
|
||||||
|
|
||||||
|
func NewClosedConnectionMatcher() *closedConnectionMatcher { //nolint:revive
|
||||||
|
return &closedConnectionMatcher{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *closedConnectionMatcher) Matches(x interface{}) bool {
|
||||||
|
context, ok := x.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
vErr, ok := context["error"]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
err, ok := vErr.(error)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Contains(err.Error(), "used of closed network connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *closedConnectionMatcher) String() string {
|
||||||
|
return "map containing error of closed network connection"
|
||||||
|
}
|
||||||
160
internal/bridge/mocks/mocks.go
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/ProtonMail/proton-bridge/v3/internal/bridge (interfaces: TLSReporter,ProxyController,Autostarter)
|
||||||
|
|
||||||
|
// Package mocks is a generated GoMock package.
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockTLSReporter is a mock of TLSReporter interface.
|
||||||
|
type MockTLSReporter struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockTLSReporterMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockTLSReporterMockRecorder is the mock recorder for MockTLSReporter.
|
||||||
|
type MockTLSReporterMockRecorder struct {
|
||||||
|
mock *MockTLSReporter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockTLSReporter creates a new mock instance.
|
||||||
|
func NewMockTLSReporter(ctrl *gomock.Controller) *MockTLSReporter {
|
||||||
|
mock := &MockTLSReporter{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockTLSReporterMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockTLSReporter) EXPECT() *MockTLSReporterMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTLSIssueCh mocks base method.
|
||||||
|
func (m *MockTLSReporter) GetTLSIssueCh() <-chan struct{} {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetTLSIssueCh")
|
||||||
|
ret0, _ := ret[0].(<-chan struct{})
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTLSIssueCh indicates an expected call of GetTLSIssueCh.
|
||||||
|
func (mr *MockTLSReporterMockRecorder) GetTLSIssueCh() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTLSIssueCh", reflect.TypeOf((*MockTLSReporter)(nil).GetTLSIssueCh))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockProxyController is a mock of ProxyController interface.
|
||||||
|
type MockProxyController struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockProxyControllerMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockProxyControllerMockRecorder is the mock recorder for MockProxyController.
|
||||||
|
type MockProxyControllerMockRecorder struct {
|
||||||
|
mock *MockProxyController
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockProxyController creates a new mock instance.
|
||||||
|
func NewMockProxyController(ctrl *gomock.Controller) *MockProxyController {
|
||||||
|
mock := &MockProxyController{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockProxyControllerMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockProxyController) EXPECT() *MockProxyControllerMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowProxy mocks base method.
|
||||||
|
func (m *MockProxyController) AllowProxy() {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "AllowProxy")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowProxy indicates an expected call of AllowProxy.
|
||||||
|
func (mr *MockProxyControllerMockRecorder) AllowProxy() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllowProxy", reflect.TypeOf((*MockProxyController)(nil).AllowProxy))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisallowProxy mocks base method.
|
||||||
|
func (m *MockProxyController) DisallowProxy() {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "DisallowProxy")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisallowProxy indicates an expected call of DisallowProxy.
|
||||||
|
func (mr *MockProxyControllerMockRecorder) DisallowProxy() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisallowProxy", reflect.TypeOf((*MockProxyController)(nil).DisallowProxy))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAutostarter is a mock of Autostarter interface.
|
||||||
|
type MockAutostarter struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockAutostarterMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAutostarterMockRecorder is the mock recorder for MockAutostarter.
|
||||||
|
type MockAutostarterMockRecorder struct {
|
||||||
|
mock *MockAutostarter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockAutostarter creates a new mock instance.
|
||||||
|
func NewMockAutostarter(ctrl *gomock.Controller) *MockAutostarter {
|
||||||
|
mock := &MockAutostarter{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockAutostarterMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockAutostarter) EXPECT() *MockAutostarterMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable mocks base method.
|
||||||
|
func (m *MockAutostarter) Disable() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Disable")
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable indicates an expected call of Disable.
|
||||||
|
func (mr *MockAutostarterMockRecorder) Disable() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disable", reflect.TypeOf((*MockAutostarter)(nil).Disable))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable mocks base method.
|
||||||
|
func (m *MockAutostarter) Enable() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Enable")
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable indicates an expected call of Enable.
|
||||||
|
func (mr *MockAutostarterMockRecorder) Enable() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Enable", reflect.TypeOf((*MockAutostarter)(nil).Enable))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEnabled mocks base method.
|
||||||
|
func (m *MockAutostarter) IsEnabled() bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "IsEnabled")
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEnabled indicates an expected call of IsEnabled.
|
||||||
|
func (mr *MockAutostarterMockRecorder) IsEnabled() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEnabled", reflect.TypeOf((*MockAutostarter)(nil).IsEnabled))
|
||||||
|
}
|
||||||
107
internal/bridge/mocks/telemetry_mocks.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/ProtonMail/proton-bridge/v3/internal/telemetry (interfaces: HeartbeatManager)
|
||||||
|
|
||||||
|
// Package mocks is a generated GoMock package.
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
time "time"
|
||||||
|
|
||||||
|
telemetry "github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockHeartbeatManager is a mock of HeartbeatManager interface.
|
||||||
|
type MockHeartbeatManager struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockHeartbeatManagerMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockHeartbeatManagerMockRecorder is the mock recorder for MockHeartbeatManager.
|
||||||
|
type MockHeartbeatManagerMockRecorder struct {
|
||||||
|
mock *MockHeartbeatManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockHeartbeatManager creates a new mock instance.
|
||||||
|
func NewMockHeartbeatManager(ctrl *gomock.Controller) *MockHeartbeatManager {
|
||||||
|
mock := &MockHeartbeatManager{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockHeartbeatManagerMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockHeartbeatManager) EXPECT() *MockHeartbeatManagerMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeartbeatPeriodicInterval mocks base method.
|
||||||
|
func (m *MockHeartbeatManager) GetHeartbeatPeriodicInterval() time.Duration {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetHeartbeatPeriodicInterval")
|
||||||
|
ret0, _ := ret[0].(time.Duration)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeartbeatPeriodicInterval indicates an expected call of GetHeartbeatPeriodicInterval.
|
||||||
|
func (mr *MockHeartbeatManagerMockRecorder) GetHeartbeatPeriodicInterval() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHeartbeatPeriodicInterval", reflect.TypeOf((*MockHeartbeatManager)(nil).GetHeartbeatPeriodicInterval))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLastHeartbeatSent mocks base method.
|
||||||
|
func (m *MockHeartbeatManager) GetLastHeartbeatSent() time.Time {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetLastHeartbeatSent")
|
||||||
|
ret0, _ := ret[0].(time.Time)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLastHeartbeatSent indicates an expected call of GetLastHeartbeatSent.
|
||||||
|
func (mr *MockHeartbeatManagerMockRecorder) GetLastHeartbeatSent() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastHeartbeatSent", reflect.TypeOf((*MockHeartbeatManager)(nil).GetLastHeartbeatSent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTelemetryAvailable mocks base method.
|
||||||
|
func (m *MockHeartbeatManager) IsTelemetryAvailable(arg0 context.Context) bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "IsTelemetryAvailable", arg0)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTelemetryAvailable indicates an expected call of IsTelemetryAvailable.
|
||||||
|
func (mr *MockHeartbeatManagerMockRecorder) IsTelemetryAvailable(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsTelemetryAvailable", reflect.TypeOf((*MockHeartbeatManager)(nil).IsTelemetryAvailable), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendHeartbeat mocks base method.
|
||||||
|
func (m *MockHeartbeatManager) SendHeartbeat(arg0 context.Context, arg1 *telemetry.HeartbeatData) bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SendHeartbeat", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendHeartbeat indicates an expected call of SendHeartbeat.
|
||||||
|
func (mr *MockHeartbeatManagerMockRecorder) SendHeartbeat(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendHeartbeat", reflect.TypeOf((*MockHeartbeatManager)(nil).SendHeartbeat), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLastHeartbeatSent mocks base method.
|
||||||
|
func (m *MockHeartbeatManager) SetLastHeartbeatSent(arg0 time.Time) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetLastHeartbeatSent", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLastHeartbeatSent indicates an expected call of SetLastHeartbeatSent.
|
||||||
|
func (mr *MockHeartbeatManagerMockRecorder) SetLastHeartbeatSent(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastHeartbeatSent", reflect.TypeOf((*MockHeartbeatManager)(nil).SetLastHeartbeatSent), arg0)
|
||||||
|
}
|
||||||