Compare commits
1039 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| ba712516ff | |||
| 865ac44037 | |||
| 7d41062ae9 | |||
| f3c69faf8b | |||
| e353dc554d | |||
| 415d08b411 | |||
| 62499a5630 | |||
| d6d7ea592e | |||
| 5033e9718c | |||
| 16f9dc43cb | |||
| b8f27cc7d2 | |||
| 6b10da524c | |||
| 51eb2c42cd | |||
| c94d839fbb | |||
| de586e5f12 | |||
| c32a106898 | |||
| 5b20b6a3d0 | |||
| 3b07121f08 | |||
| a53bc4b027 | |||
| 478345e277 | |||
| 0ed78f1ccb | |||
| 6671dd38ea | |||
| 2d5ea669a5 | |||
| c7eb7234a2 | |||
| 73d1fe2f65 | |||
| cf75ea739f | |||
| c920c53243 | |||
| 63379001e3 | |||
| aa8cc3fc4b | |||
| 61e4ca5814 | |||
| 8e0693ab03 | |||
| a3d2df9d38 | |||
| f9f4ce996d | |||
| fc69b9aabb | |||
| f7ed3abcfe | |||
| c2ffae3799 | |||
| 437b7a4cfe | |||
| df601ecbbd | |||
| e9c05c5a6b | |||
| 22f427d522 | |||
| d356f306d9 | |||
| e717b69139 | |||
| 1dbd37d05b | |||
| 7dad6cc9a4 | |||
| 0332a3f873 | |||
| 20a0404efb | |||
| 29b7530ddf | |||
| 27e7d7967d | |||
| d40fbda2ab | |||
| 3bb9146d9f | |||
| f0d05aeb79 | |||
| ad6b84d4e0 | |||
| 38031b2fb9 | |||
| b74dba884f | |||
| 7276c23b2b | |||
| f0b1ab55a2 | |||
| f851f4d5c2 | |||
| f2d568d92f | |||
| a0dc764bb9 | |||
| 55beb9227f | |||
| 6ed97a0347 | |||
| 7ce3529f5d | |||
| ed9edb3620 | |||
| f30269865d | |||
| d7c5ace8e4 | |||
| b82e2ca176 | |||
| 5af3e930ec | |||
| 72cd641c72 | |||
| 4a2ac813d3 | |||
| 961742fa53 | |||
| 9984165798 | |||
| 551f5c3c18 | |||
| 41f2ffa4ec | |||
| 778b17c44e | |||
| 5d51cc1739 | |||
| a7270102af | |||
| ca5c45b1c5 | |||
| 31920a4468 | |||
| 2e52b8db87 | |||
| 2899e7bb15 | |||
| 41e15db442 | |||
| b5b477a3ce | |||
| 07b7fa7364 | |||
| 5637ca2529 | |||
| a93a8e7be9 | |||
| db7ead3901 | |||
| 42ced6694e | |||
| af0c5e6bae | |||
| cf6ed81a00 | |||
| 8c9d5c54fc | |||
| 36ec9b07e0 | |||
| d9847ddd6a | |||
| e1747357bc | |||
| 77e352a101 | |||
| b41c4d2fa6 | |||
| b6ad1fe490 | |||
| bc21bb1d8d | |||
| 8ea610c625 | |||
| 10da4f284c | |||
| e49d2e1be7 | |||
| b259de238e | |||
| ea821b1bd8 | |||
| 94347d95df | |||
| ef051d5ed6 | |||
| e40e8e3e75 | |||
| 9d0368de97 | |||
| c5699700b3 | |||
| 1141ea27e2 | |||
| ecc1c34b16 | |||
| 3601adcae6 | |||
| d11cf57879 | |||
| 2c8feff97a | |||
| b7adccf651 | |||
| 726c8918ab | |||
| 29af8e7178 | |||
| 18257f0302 | |||
| aeceb7d593 | |||
| 85c06809d2 | |||
| f65e050588 | |||
| e0d07d67a0 | |||
| 0a9748a15d | |||
| 6bd0739013 | |||
| 5cb893fc1b | |||
| f5624c9932 | |||
| 2b1daa60bb | |||
| ffb18adfd0 | |||
| 649195cc2b | |||
| b0ce46ca8a | |||
| 6435f7b09a | |||
| 59075f2e26 | |||
| 1d9855a190 | |||
| cea33bebe2 | |||
| 9d405a1549 | |||
| 47f468e4b7 | |||
| b9c6c00709 | |||
| 5ce9cb8eec | |||
| bc7133e401 | |||
| a219ecf3cb | |||
| 8061b1e6fa | |||
| 6509df523f | |||
| 0d1abaec0d | |||
| 4d1ace5de7 | |||
| 1250621a4d | |||
| 8d6e55ba54 | |||
| a4a29cbf82 | |||
| 39bccc2747 | |||
| 0cf1b38c2b | |||
| 6b7e706100 | |||
| bb90ed5446 | |||
| 107843d58f | |||
| 63f089540e | |||
| c35ff4f913 | |||
| cd6b5cdcc3 | |||
| 8f1ca00c9d | |||
| f21f583d05 | |||
| e940d9f6fe | |||
| 1ed8e939b8 | |||
| b5d63783f7 | |||
| e0113ec267 | |||
| 22d2bcc21d | |||
| 91dcb2f773 | |||
| c676c732ab | |||
| 444f2d8a12 | |||
| f10da3c7f0 | |||
| b8dd9f82bd | |||
| 1157e60972 | |||
| e9e4d8c725 | |||
| 186fa24106 | |||
| 63780b7b8d | |||
| e3e4769d78 | |||
| b2e9c4e4e9 | |||
| db4cb36538 | |||
| 984864553e | |||
| 2707a5627c | |||
| 8e0d6d41a6 | |||
| 2b76a45e17 | |||
| 7ab54da8c4 | |||
| 2cce29e858 | |||
| ef1223391b | |||
| fb98a797ba | |||
| 6be31bdeb2 | |||
| fce5990d19 | |||
| 1d4ee0c33e | |||
| a3e102e456 | |||
| 21dcac9fac | |||
| f0ee82fdd2 | |||
| 0c6a098af9 | |||
| 5bf359d34f | |||
| 6d784f2444 | |||
| 835bf1e77f | |||
| df5fbda72f | |||
| c482f768d9 | |||
| 21cf7459c9 | |||
| cf1ba6588a | |||
| 858f2c7f29 | |||
| f63238faed | |||
| f6ff85f69d | |||
| ec5b5939b9 | |||
| dec00ff9cc | |||
| 9fddd77f0d | |||
| aae65c9d38 | |||
| f3b197fa56 | |||
| 0a9ce5f526 | |||
| a2029002c4 | |||
| 7c41c8e23a | |||
| 36fdb88d96 | |||
| c69239ca16 | |||
| e10aa89313 | |||
| d0a97a3f4a | |||
| e01dc77a61 | |||
| 509ba52ba2 | |||
| c37a0338c5 | |||
| 9f23d5a6f4 | |||
| 885fb95454 | |||
| 629d6c5e4d | |||
| 3f50bf66f4 | |||
| 4072205709 | |||
| 233c55ab19 | |||
| 5d82c218ca | |||
| cb30dd91e3 | |||
| 41d82e10f9 | |||
| 8496c9e181 | |||
| 3dadad5131 | |||
| 00146e7474 | |||
| 12ac47e949 | |||
| 6ff4c8a738 | |||
| dd66b7f8d0 | |||
| 0b95ed4dea | |||
| ce64aeb05f | |||
| 27cfda680d | |||
| 323303a98b | |||
| 8109831c07 | |||
| 2284e9ede1 | |||
| 1d538e8540 | |||
| 8ccaac8090 | |||
| 22bf8f62ce | |||
| fed031ebaa | |||
| 7a15ebbd54 | |||
| 94b5799ba7 | |||
| 286f51a4e7 | |||
| ee961ae4a8 | |||
| 4038752a9a | |||
| ebf724412b | |||
| 14d42b5e76 | |||
| 2b8d92e82d | |||
| 11b1e3acf5 | |||
| c5eb660315 | |||
| 5ad23715ec | |||
| 8ab05a000c | |||
| 454d248819 | |||
| 6c8e5f7cd3 | |||
| f5aba717b2 | |||
| 1359c39bc0 | |||
| 4850681f1d | |||
| aa55c69307 | |||
| 1f19d4df75 | |||
| c0f6af9eb5 | |||
| ef6a3d4999 | |||
| 50550d42b4 | |||
| 8db89a1a6c | |||
| ba1dfb1bf4 | |||
| d243880753 | |||
| cccaaa3d82 | |||
| 2d95f21567 | |||
| 7d0af7624c | |||
| 2f35c453a1 | |||
| 05dd137bc8 | |||
| 767628946f | |||
| d4efa7131f | |||
| 144cf6e40c | |||
| a205d8c046 | |||
| cccadaee42 | |||
| bbb365f8a5 | |||
| 1f18d9d917 | |||
| 59e0d63485 | |||
| 72fe5a636e | |||
| 45a83133ba | |||
| 215eb4d6eb | |||
| 479b951c50 | |||
| a94c8a943f | |||
| ea306f405e | |||
| 1b405506b8 | |||
| 38c6132f81 | |||
| b7351dfaf8 | |||
| 7e8f6943f2 | |||
| a0132e8440 | |||
| 27541784aa | |||
| 9e567f08b2 | |||
| bf274f984e | |||
| 3b60bbe13b | |||
| a73a1b623a | |||
| c0a8877018 | |||
| 904166c01c | |||
| 4761bc935a | |||
| 71301d891f | |||
| d47be3c4c0 | |||
| 199a4d1e3a | |||
| 18668aafc9 | |||
| fd73ec6861 | |||
| feeb7179f5 | |||
| 0e5a45671f | |||
| 2beb0d298e | |||
| 22a6fcd87f | |||
| f499252444 | |||
| b27e3fdb28 | |||
| 415e56d928 | |||
| 845074f421 | |||
| 28f46deef9 | |||
| 2a078b76e6 | |||
| 3428557b15 | |||
| 1f25aeab31 | |||
| 4e531d4524 | |||
| 7fc7083c76 | |||
| 0fe69d9de1 | |||
| 8b436186a4 | |||
| 4d000c2376 | |||
| 56bce8e06f | |||
| 6fd614595d | |||
| 7bb7e1a518 | |||
| fb89fb7b31 | |||
| e6ae344f1f | |||
| bad8cad97d | |||
| 77cd2955f1 | |||
| 567b65df8d | |||
| 06b3ed9b85 | |||
| 565c0b6ddf | |||
| df318382b7 | |||
| 341487d839 | |||
| 7468ed7dc0 | |||
| c6107dbd4b | |||
| bcef1c36ba | |||
| 6d9d5f35ca | |||
| 6299a6d390 | |||
| 4ab5635293 | |||
| 5c9e9caa2f | |||
| e055acb8eb | |||
| 72c01046e3 | |||
| 46bc8b08dc | |||
| 00b5046653 | |||
| 21d8ef649f | |||
| 6c96643d12 | |||
| 9193205834 | |||
| 15df130d76 | |||
| 4554369292 | |||
| 50d167a983 | |||
| 7065211064 | |||
| 0069eb9a0c | |||
| 35b5b925cf | |||
| d4df2ea348 | |||
| 0159f24f17 | |||
| 619d5eaec9 | |||
| 52804c7039 | |||
| 837e0d3758 | |||
| fa3829cbfe | |||
| 0679b99a65 | |||
| 4ffa62f6ca | |||
| 0c458f709f | |||
| d1daa02b35 | |||
| 26cdfdeba9 | |||
| f4405b5186 | |||
| 76dda10572 | |||
| 0cde1ab801 | |||
| d9c9edf4d7 | |||
| 29f034abdc | |||
| 62a64cde61 | |||
| 9747145a3c | |||
| e2a30d1ac6 | |||
| 3168cbb77d | |||
| 0a0cc0a62c | |||
| adcf0827ee | |||
| b9ee4a152a | |||
| cb839ff149 | |||
| 45efdad27e | |||
| 6ef2bb254d | |||
| 645a8257d9 | |||
| 8ab852277c | |||
| 516ca018d3 | |||
| 5117672388 | |||
| a468ce635c | |||
| 5c58089fb7 | |||
| b3a64892fe | |||
| 3e9c4ba614 | |||
| 8cd17addbe | |||
| 2feaba8888 | |||
| 1909ceed67 | |||
| 07c100bd66 | |||
| ab4776c332 | |||
| f17e0d761e | |||
| a5b9f4c3f1 | |||
| a72f52a5ed | |||
| 6523b906af | |||
| 5d246d449c | |||
| 0b39b2adf6 | |||
| 25a8c1962b | |||
| 9bb7c828cd | |||
| f18dcf9db3 | |||
| 44f8a49b47 | |||
| 10301b8600 | |||
| 32db6b8d44 | |||
| debf015dd0 | |||
| 4013892a47 | |||
| d5277454c6 | |||
| a9f44731dc | |||
| 48808992ec | |||
| 036bc88789 | |||
| 67a7d556ec | |||
| 5ad338e835 | |||
| e442c47eed | |||
| 5380edeeb9 | |||
| e50d1d01da | |||
| 082a803e47 | |||
| 07d9bc0831 | |||
| 4e5a1d4b30 | |||
| 4a54f878c4 | |||
| dc3b4d53e1 | |||
| be583c431e | |||
| 805544ffb0 | |||
| 7b84038bf4 | |||
| e8cbbaa832 | |||
| 56e32e67de | |||
| b36ac532c9 | |||
| d3b0871cf1 | |||
| 7b4204591c | |||
| 4514d72d70 | |||
| dfbf25a9f4 | |||
| 122eac50a6 | |||
| 839708dcfe | |||
| d2066173f0 | |||
| eccad4bbfd | |||
| 98ab794f13 | |||
| b7b2297635 | |||
| dc3f61acee | |||
| 6fffb460b8 | |||
| c677b78f16 | |||
| 014c8af560 | |||
| 2f149eb545 | |||
| 175f0977f9 | |||
| 8fe042218c | |||
| 9fe3718d3f | |||
| 1839f072b4 | |||
| a2cf1b6022 | |||
| 0a4fb4594a | |||
| eb1176eba6 | |||
| a89dfc4524 | |||
| 1ba378dace | |||
| 25c1014ab0 | |||
| 363f553d3c | |||
| 36a6f1ed4b | |||
| 7b112fc448 | |||
| 2a0052dda6 | |||
| a7b32e1330 | |||
| 5adfdc19aa | |||
| 351c86ee7a | |||
| dce7888408 | |||
| a2d99fa6b7 | |||
| 79465571d7 | |||
| 9d576beeb8 | |||
| e3332d1cb6 | |||
| f59f68f894 | |||
| 9c881a02d6 | |||
| 7b21c2d734 | |||
| 9fdc5960bf | |||
| fe853efe32 | |||
| 9b82c03959 | |||
| 914d1b27b5 | |||
| f295d03641 | |||
| 8515f6e6ac | |||
| 4d330e24c1 | |||
| a7a52bc57e | |||
| 3cef7985d3 | |||
| 40db822450 | |||
| 2de202ca02 | |||
| 38eb9fdac7 | |||
| f469d34781 | |||
| 33dfc5ce09 | |||
| 2100e2ff7c | |||
| e9b7cce138 | |||
| 6877a5a15d | |||
| 64206e69bd | |||
| 7643c76cb1 | |||
| b0f59273d3 | |||
| af8eb9d37d | |||
| 635e51f32f | |||
| ca962ce5ad | |||
| a50266cdc0 | |||
| 6230200218 | |||
| f96cd167ef | |||
| 072ce54fe1 | |||
| d043cb9086 | |||
| 1f31df3a94 | |||
| 9ee30e4923 | |||
| 7b44f12ab1 | |||
| 874882b554 | |||
| 945bdf4c60 | |||
| 6e1e5a2afe | |||
| b709b51790 | |||
| d380485bb6 | |||
| 87c8228cd0 | |||
| 152046bf97 | |||
| a0fbed5859 | |||
| 89e9e17d26 | |||
| b595247392 | |||
| 9d50a8cef2 | |||
| f888176485 | |||
| 2f9876ad74 | |||
| 53404122cc | |||
| ba65494fce |
2
.gitattributes
vendored
@ -1 +1 @@
|
||||
Changelog.md merge=union
|
||||
unreleased.md merge=union
|
||||
|
||||
@ -27,6 +27,9 @@ Issue tracker is ONLY used for reporting bugs with technical details. "It doesn'
|
||||
3.
|
||||
4.
|
||||
|
||||
## Version Information
|
||||
<!--- Which version of the app(s) were you using when you experienced this issue? -->
|
||||
|
||||
## Context (Environment)
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||
|
||||
46
.gitignore
vendored
@ -5,43 +5,39 @@
|
||||
# Editor files
|
||||
.*.sw?
|
||||
*~
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
vendor
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# Test files
|
||||
godog.test
|
||||
debug.test
|
||||
coverage.html
|
||||
gobinsec-cache*.yml
|
||||
|
||||
# Run files
|
||||
mem.pprof
|
||||
|
||||
# Auto generated frontend
|
||||
internal/frontend/qml/BridgeUI/*.qmlc
|
||||
internal/frontend/qml/ImportExportUI/*.qmlc
|
||||
internal/frontend/qml/ProtonUI/*.qmlc
|
||||
internal/frontend/qml/ProtonUI/fontawesome.ttf
|
||||
internal/frontend/qml/ProtonUI/images
|
||||
internal/frontend/qml/ImportExportUI/images
|
||||
frontend/qml/*.qmlc
|
||||
# Auto generated
|
||||
internal/**/credits.go
|
||||
vendor
|
||||
vendor-cache
|
||||
/main.go
|
||||
|
||||
|
||||
# Build files
|
||||
bridge_darwin_*.tgz
|
||||
/launcher-*
|
||||
/bridge_*_*.tgz
|
||||
/ie_*_*.tgz
|
||||
/versioner
|
||||
/hasher
|
||||
cmd/Desktop-Bridge/deploy
|
||||
cmd/Import-Export/deploy
|
||||
internal/frontend/qt*/moc.cpp
|
||||
internal/frontend/qt*/moc.go
|
||||
internal/frontend/qt*/moc.h
|
||||
internal/frontend/qt*/moc_cgo_*.go
|
||||
internal/frontend/qt*/moc_moc.h
|
||||
internal/frontend/qt*/rcc.cpp
|
||||
internal/frontend/qt*/rcc.qrc
|
||||
internal/frontend/qt*/rcc_cgo_*.go
|
||||
proton-bridge
|
||||
cmd/Desktop-Bridge/*.exe
|
||||
cmd/launcher/*.exe
|
||||
|
||||
internal/frontend/rcc.cpp
|
||||
internal/frontend/rcc.qrc
|
||||
internal/frontend/rcc_cgo_*.go
|
||||
vendor-cache/
|
||||
# Jetbrains (CLion, Golang) cmake build dirs
|
||||
cmake-build-*/
|
||||
|
||||
/main.go
|
||||
# Doxygen doc files
|
||||
_doc/
|
||||
|
||||
316
.gitlab-ci.yml
@ -1,72 +1,116 @@
|
||||
image: gitlab.protontech.ch:4567/go/bridge-internal
|
||||
# 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: harbor.protontech.ch/docker.io/library/golang:1.18
|
||||
|
||||
variables:
|
||||
GOPRIVATE: gitlab.protontech.ch
|
||||
GOMAXPROCS: $(( ${CI_TAG_CPU} / 2 ))
|
||||
|
||||
before_script:
|
||||
- eval $(ssh-agent -s)
|
||||
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
|
||||
|
||||
- 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
|
||||
- apt update && apt-get -y install libsecret-1-dev
|
||||
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
||||
|
||||
stages:
|
||||
- cache
|
||||
- test
|
||||
- build
|
||||
- mirror
|
||||
|
||||
# Stage: CACHE
|
||||
.rules-branch-and-MR-always:
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
when: always
|
||||
allow_failure: false
|
||||
- when: never
|
||||
|
||||
.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-always:
|
||||
rules:
|
||||
- if: $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-MR-always-allow-failure:
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
when: always
|
||||
allow_failure: true
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
when: manual
|
||||
allow_failure: true
|
||||
- when: never
|
||||
|
||||
# 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
|
||||
extends:
|
||||
- .rules-branch-and-MR-always
|
||||
script:
|
||||
- make lint
|
||||
tags:
|
||||
- medium
|
||||
|
||||
test:
|
||||
test-linux:
|
||||
stage: test
|
||||
only:
|
||||
- branches
|
||||
extends:
|
||||
- .rules-branch-manual-MR-always
|
||||
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-linux-race:
|
||||
stage: test
|
||||
extends:
|
||||
- .rules-branch-manual-MR-always-allow-failure
|
||||
script:
|
||||
- make test-race
|
||||
tags:
|
||||
- medium
|
||||
|
||||
test-integration:
|
||||
stage: test
|
||||
only:
|
||||
- branches
|
||||
extends:
|
||||
- .rules-branch-manual-MR-always
|
||||
script:
|
||||
- VERBOSITY=debug make -C test test
|
||||
- make test-integration
|
||||
tags:
|
||||
- large
|
||||
|
||||
test-integration-race:
|
||||
stage: test
|
||||
extends:
|
||||
- .rules-branch-manual-MR-always-allow-failure
|
||||
script:
|
||||
- make test-integration-race
|
||||
tags:
|
||||
- large
|
||||
|
||||
dependency-updates:
|
||||
stage: test
|
||||
@ -77,47 +121,63 @@ dependency-updates:
|
||||
|
||||
.build-base:
|
||||
stage: build
|
||||
only:
|
||||
- branches
|
||||
needs: ["lint"]
|
||||
rules:
|
||||
# GODT-1833: use `=~ /qa/` after mac and windows runners are fixed
|
||||
- if: $CI_JOB_NAME =~ /build-linux-qa/ && $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
when: always
|
||||
allow_failure: false
|
||||
- if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
when: manual
|
||||
allow_failure: true
|
||||
- when: never
|
||||
before_script:
|
||||
- mkdir -p .cache/bin
|
||||
- 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}
|
||||
script:
|
||||
- make build
|
||||
- git diff && git diff-index --quiet HEAD
|
||||
- make vault-editor
|
||||
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.
|
||||
# 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
|
||||
when: always
|
||||
paths:
|
||||
- bridge_*.tgz
|
||||
- vault-editor
|
||||
tags:
|
||||
- large
|
||||
|
||||
build-linux:
|
||||
extends: .build-base
|
||||
image: gitlab.protontech.ch:4567/go/bridge-internal:qt6
|
||||
variables:
|
||||
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
||||
cache:
|
||||
key: linux-vcpkg
|
||||
paths:
|
||||
- .cache
|
||||
when: 'always'
|
||||
artifacts:
|
||||
name: "bridge-linux-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- bridge_*.tgz
|
||||
|
||||
build-linux-qa:
|
||||
extends: .build-base
|
||||
only:
|
||||
- web
|
||||
script:
|
||||
- BUILD_TAGS="build_qa pmapi_qa" make build
|
||||
extends: build-linux
|
||||
variables:
|
||||
BUILD_TAGS: "build_qa"
|
||||
artifacts:
|
||||
name: "bridge-linux-qa-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- bridge_*.tgz
|
||||
|
||||
build-ie-linux:
|
||||
extends: .build-base
|
||||
script:
|
||||
- make build-ie
|
||||
artifacts:
|
||||
name: "ie-linux-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ie_*.tgz
|
||||
|
||||
.build-darwin-base:
|
||||
extends: .build-base
|
||||
before_script:
|
||||
- eval $(ssh-agent -s)
|
||||
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
|
||||
- export PATH=/usr/local/bin:$PATH
|
||||
- export PATH=/usr/local/opt/git/bin:$PATH
|
||||
- export PATH=/usr/local/opt/make/libexec/gnubin:$PATH
|
||||
@ -126,6 +186,13 @@ build-ie-linux:
|
||||
- export GOPATH=~/go
|
||||
- export PATH=$GOPATH/bin:$PATH
|
||||
- export CGO_CPPFLAGS='-Wno-error -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-builtin-requires-header'
|
||||
- $(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}
|
||||
script:
|
||||
- go version
|
||||
- make build-nogui
|
||||
- git diff && git diff-index --quiet HEAD
|
||||
- make vault-editor
|
||||
cache: {}
|
||||
tags:
|
||||
- macOS
|
||||
@ -134,110 +201,47 @@ 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
|
||||
script:
|
||||
- BUILD_TAGS="build_qa pmapi_qa" make build
|
||||
variables:
|
||||
BUILD_TAGS: "build_qa"
|
||||
artifacts:
|
||||
name: "bridge-darwin-qa-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- bridge_*.tgz
|
||||
|
||||
build-ie-darwin:
|
||||
extends: .build-darwin-base
|
||||
script:
|
||||
- make build-ie
|
||||
artifacts:
|
||||
name: "ie-darwin-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ie_*.tgz
|
||||
|
||||
.build-windows-base:
|
||||
extends: .build-base
|
||||
services:
|
||||
- docker:dind
|
||||
variables:
|
||||
DOCKER_HOST: tcp://docker:2375
|
||||
before_script:
|
||||
- export GOROOT=/c/Go1.18/
|
||||
- export PATH=$GOROOT/bin:$PATH
|
||||
- export GOARCH=amd64
|
||||
- export GOPATH=~/go18
|
||||
- export GO111MODULE=on
|
||||
- export PATH="${GOPATH}/bin:${PATH}"
|
||||
- export MSYSTEM=
|
||||
- export QT6DIR=/c/grrrQt/6.3.1/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}
|
||||
script:
|
||||
- make build-nogui
|
||||
- git diff && git diff-index --quiet HEAD
|
||||
- make vault-editor
|
||||
tags:
|
||||
- windows-bridge
|
||||
|
||||
build-windows:
|
||||
extends: .build-windows-base
|
||||
script:
|
||||
# We need to install docker because qtdeploy builds for windows inside a docker container.
|
||||
# Docker will connect to the dockerd daemon provided by the runner service docker:dind at tcp://docker:2375.
|
||||
- curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
||||
- go mod download
|
||||
- TARGET_OS=windows make build
|
||||
artifacts:
|
||||
name: "bridge-windows-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- bridge_*.tgz
|
||||
|
||||
build-windows-qa:
|
||||
extends: .build-windows-base
|
||||
only:
|
||||
- web
|
||||
script:
|
||||
# We need to install docker because qtdeploy builds for windows inside a docker container.
|
||||
# Docker will connect to the dockerd daemon provided by the runner service docker:dind at tcp://docker:2375.
|
||||
- curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
||||
- go mod download
|
||||
- TARGET_OS=windows BUILD_TAGS="build_qa pmapi_qa" make build
|
||||
variables:
|
||||
BUILD_TAGS: "build_qa"
|
||||
artifacts:
|
||||
name: "bridge-windows-qa-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- bridge_*.tgz
|
||||
|
||||
build-ie-windows:
|
||||
extends: .build-windows-base
|
||||
script:
|
||||
# We need to install docker because qtdeploy builds for windows inside a docker container.
|
||||
# Docker will connect to the dockerd daemon provided by the runner service docker:dind at tcp://docker:2375.
|
||||
- curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
||||
- go mod download
|
||||
- TARGET_OS=windows make build-ie
|
||||
artifacts:
|
||||
name: "ie-windows-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ie_*.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)"
|
||||
# TODO: PUT BACK ALL THE JOBS! JUST DID THIS FOR NOW TO GET CI WORKING AGAIN...
|
||||
|
||||
3
.gitmodules
vendored
@ -0,0 +1,3 @@
|
||||
[submodule "submodules/vcpkg"]
|
||||
path = extern/vcpkg
|
||||
url = https://github.com/Microsoft/vcpkg.git
|
||||
|
||||
@ -1,16 +1,23 @@
|
||||
---
|
||||
run:
|
||||
timeout: 10m
|
||||
build-tags:
|
||||
- nogui
|
||||
skip-dirs:
|
||||
- pkg/mime
|
||||
- extern
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
exclude:
|
||||
- Using the variable on range scope `tt` in function literal
|
||||
- should have comment (\([^)]+\) )?or be unexported # For now we are missing a lot of comments.
|
||||
- at least one file in a package should have a package comment # For now we are missing a lot of comments.
|
||||
- Using the variable on range scope `tt` in function literal
|
||||
# For now we are missing a lot of comments.
|
||||
- should have comment (\([^)]+\) )?or be unexported
|
||||
# For now we are missing a lot of comments.
|
||||
- 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:
|
||||
- path: _test\.go
|
||||
@ -20,6 +27,17 @@ issues:
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gosec
|
||||
- goconst
|
||||
- dogsled
|
||||
- path: test
|
||||
linters:
|
||||
- dupl
|
||||
- funlen
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gosec
|
||||
- goconst
|
||||
- dogsled
|
||||
|
||||
linters-settings:
|
||||
godox:
|
||||
@ -30,7 +48,7 @@ linters-settings:
|
||||
linters:
|
||||
# setting disable-all will make only explicitly enabled linters run
|
||||
disable-all: true
|
||||
|
||||
|
||||
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]
|
||||
@ -49,24 +67,58 @@ linters:
|
||||
- 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]
|
||||
- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
|
||||
#- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
|
||||
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
|
||||
- gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false]
|
||||
- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
|
||||
- godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false]
|
||||
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
|
||||
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
|
||||
- golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
|
||||
- gosec # Inspects source code for security problems [fast: true, auto-fix: false]
|
||||
- interfacer # Linter that suggests narrower interface types [fast: true, auto-fix: false]
|
||||
- maligned # Tool to detect Go structs that would take less memory if their fields were sorted [fast: true, auto-fix: false]
|
||||
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
|
||||
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
|
||||
- prealloc # Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
|
||||
- scopelint # Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false]
|
||||
- stylecheck # Stylecheck is a replacement for golint [fast: true, auto-fix: false]
|
||||
- unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false]
|
||||
- unparam # Reports unused function parameters [fast: true, auto-fix: false]
|
||||
- whitespace # Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true]
|
||||
#- wsl # Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false]
|
||||
#- lll # Reports long lines [fast: true, auto-fix: false]
|
||||
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false]
|
||||
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
||||
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
|
||||
- exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
|
||||
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
|
||||
- godot # Check if comments end in a period [fast: true, auto-fix: true]
|
||||
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: false]
|
||||
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. [fast: true, auto-fix: false]
|
||||
- goprintffuncname # Checks that printf-like functions are named with `f` at the end [fast: true, auto-fix: false]
|
||||
- importas # Enforces consistent import aliases [fast: false, auto-fix: false]
|
||||
- makezero # Finds slice declarations with non-zero initial length [fast: false, auto-fix: false]
|
||||
- nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false]
|
||||
- predeclared # find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false]
|
||||
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false]
|
||||
- rowserrcheck # checks whether Err of rows is checked successfully [fast: false, auto-fix: false]
|
||||
- sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. [fast: false, auto-fix: false]
|
||||
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes [fast: false, auto-fix: false]
|
||||
- wastedassign # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false]
|
||||
# - wsl # Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false]
|
||||
# - lll # Reports long lines [fast: true, auto-fix: false]
|
||||
# Consider to include:
|
||||
# - gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
|
||||
# - cyclop # checks function and package cyclomatic complexity [fast: false, auto-fix: false]
|
||||
# - errorlint # go-errorlint is a source code linter for Go software that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false]
|
||||
# - exhaustivestruct # Checks if all struct's fields are initialized [fast: false, auto-fix: false]
|
||||
# - forbidigo # Forbids identifiers [fast: true, auto-fix: false]
|
||||
# - gci # Gci control golang package import order and make it always deterministic. [fast: true, auto-fix: true]
|
||||
# - gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
|
||||
# - goerr113 # Golang linter to check the errors handling expressions [fast: false, auto-fix: false]
|
||||
# - gofumpt # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true]
|
||||
# - gomnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
|
||||
# - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false]
|
||||
# - ifshort # Checks that your code uses short syntax for if-statements whenever possible [fast: true, auto-fix: false]
|
||||
# - nestif # Reports deeply nested if statements [fast: true, auto-fix: false]
|
||||
# - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false]
|
||||
# - noctx # noctx finds sending http request without context.Context [fast: false, auto-fix: false]
|
||||
# - nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false]
|
||||
# - paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test [fast: true, auto-fix: false]
|
||||
# - testpackage # linter that makes you use a separate _test package [fast: true, 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]
|
||||
|
||||
36
BUILDS.md
@ -1,10 +1,12 @@
|
||||
# Building ProtonMail Bridge and Import-Export app
|
||||
# Building Proton Mail Bridge
|
||||
|
||||
## Prerequisites
|
||||
* Go 1.13
|
||||
* 64-bit OS:
|
||||
- the go-rfc5322 module cannot currently be compiled for 32-bit OSes
|
||||
* Go 1.18
|
||||
* 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/)
|
||||
* GCC (linux, windows) or Xcode (macOS)
|
||||
- For Windows it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
|
||||
* GCC (linux), msvc (windows) or Xcode (macOS)
|
||||
* Windres (windows)
|
||||
* libglvnd and libsecret development files (linux)
|
||||
|
||||
@ -13,7 +15,11 @@ To enable the sending of crash reports using Sentry please set the
|
||||
Otherwise, the sending of crash reports will be disabled.
|
||||
|
||||
## Build
|
||||
* for Windows please unset the `MSYSTEM` variable
|
||||
In order to build Bridge app with Qt interface we are using
|
||||
[Qt 6.3](https://doc.qt.io/qt-6/gettingstarted.html).
|
||||
|
||||
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
|
||||
export MSYSTEM=
|
||||
@ -31,19 +37,23 @@ make build
|
||||
* 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`)
|
||||
|
||||
### Build Import-Export
|
||||
* in project root run
|
||||
#### Build Bridge without GUI
|
||||
* If you need to build bridge without Qt dependencies, you can do so by running
|
||||
|
||||
```bash
|
||||
make build-ie
|
||||
make build-nogui
|
||||
```
|
||||
|
||||
* 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`)
|
||||
* 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)
|
||||
* Bridge always has the option (whether built with Qt or without) to use a CLI interface by starting it with the argument `-c`
|
||||
* NOTE: You still need to setup supported keychain on your system
|
||||
|
||||
### Tags
|
||||
## Launchers
|
||||
Launchers are only included in official distributions and provide the public
|
||||
key used to verify signed app binaries, allowing the automatic update feature.
|
||||
See README for more information.
|
||||
|
||||
## Tags
|
||||
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
|
||||
starts with `br-` and Import-Export tags starts with `ie-`. Both tags continue
|
||||
|
||||
@ -2,8 +2,7 @@
|
||||
|
||||
By making a contribution to this project:
|
||||
|
||||
1. I assign any and all copyright related to the contribution to
|
||||
Proton Technologies AG;
|
||||
1. I assign any and all copyright related to the contribution to Proton AG;
|
||||
2. I certify that the contribution was created in whole by me;
|
||||
3. I understand and agree that this project and the contribution are public
|
||||
and that a record of the contribution (including all personal information I
|
||||
|
||||
73
COPYING.md
@ -1,73 +0,0 @@
|
||||
# Copying
|
||||
Copyright (c) 2020 Proton Technologies AG
|
||||
|
||||
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.
|
||||
|
||||
|
||||
# Dependencies
|
||||
ProtonMail Bridge app includes the following libraries from Proton Technologies AG:
|
||||
|
||||
* [GopenPGP library](https://gopenpgp.org/) | The [MIT License](https://github.com/ProtonMail/gopenpgp/blob/master/LICENSE).
|
||||
|
||||
ProtonMail Bridge includes the following 3rd party software:
|
||||
|
||||
* [The Go Project libraries](https://golang.org/project/) | Available under [BSD license](https://golang.org/LICENSE)
|
||||
* [Qt Go binding](https://github.com/therecipe/qt) | Available under [LGPLv3 license](https://github.com/therecipe/qt/blob/master/LICENSE)
|
||||
* [Qt](https://www.qt.io/) | Available under [multiple licences](https://www.qt.io/licensing)
|
||||
* [Font Awesome 4.7.0](https://fontawesome.com/v4.7.0/) | Available under [multiple licenses](https://fontawesome.com/v4.7.0/license/)
|
||||
|
||||
* [notificator](https://github.com/0xAX/notificator) | Available under [license](https://github.com/0xAX/notificator/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)
|
||||
* [singleinstance](https://github.com/allan-simon/go-singleinstance) | Available under [license](https://github.com/allan-simon/go-singleinstance/blob/master/LICENSE)
|
||||
* [cascadia](https://github.com/andybalholm/cascadia) | Available under [license](https://github.com/andybalholm/cascadia/blob/master/LICENSE)
|
||||
* [gocertifi](https://github.com/certifi/gocertifi) | Available under [license](https://github.com/certifi/gocertifi/blob/master/LICENSE)
|
||||
* [logex](https://github.com/chzyer/logex) | Available under [license](https://github.com/chzyer/logex/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)
|
||||
* [wincred](https://github.com/danieljoos/wincred) | Available under [license](https://github.com/danieljoos/wincred/blob/master/LICENSE)
|
||||
* [credential-helpers](https://github.com/docker/docker-credential-helpers) | Available under [license](https://github.com/docker/docker-credential-helpers/blob/master/LICENSE)
|
||||
* [imap](https://github.com/emersion/go-imap) | Available under [license](https://github.com/emersion/go-imap/blob/master/LICENSE)
|
||||
* [imap-appendlimit](https://github.com/emersion/go-imap-appendlimit) | Available under [license](https://github.com/emersion/go-imap-appendlimit/blob/master/LICENSE)
|
||||
* [imap-idle](https://github.com/emersion/go-imap-idle) | Available under [license](https://github.com/emersion/go-imap-idle/blob/master/LICENSE)
|
||||
* [imap-quota](https://github.com/emersion/go-imap-quota) | Available under [license](https://github.com/emersion/go-imap-quota/blob/master/LICENSE)
|
||||
* [imap-specialuse](https://github.com/emersion/go-imap-specialuse) | Available under [license](https://github.com/emersion/go-imap-specialuse/blob/master/LICENSE)
|
||||
* [sasl](https://github.com/emersion/go-sasl) | Available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE)
|
||||
* [smtp](https://github.com/emersion/go-smtp) | Available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE)
|
||||
* [textwrapper](https://github.com/emersion/go-textwrapper) | Available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE)
|
||||
* [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.md)
|
||||
* [shlex](https://github.com/flynn-archive/go-shlex) | Available under [license](https://github.com/flynn-archive/go-shlex/blob/master/COPYING)
|
||||
* [raven](https://github.com/getsentry/raven-go) | Available under [license](https://github.com/getsentry/raven-go/blob/master/LICENSE)
|
||||
* [resty](https://github.com/go-resty/resty) | Available under [license](https://github.com/go-resty/resty/blob/master/LICENSE)
|
||||
* [mock](https://github.com/golang/mock) | Available under [license](https://github.com/golang/mock/blob/master/LICENSE)
|
||||
* [cmp](https://github.com/google/go-cmp) | Available under [license](https://github.com/google/go-cmp/blob/master/LICENSE)
|
||||
* [gopherjs](https://github.com/gopherjs/gopherjs) | Available under [license](https://github.com/gopherjs/gopherjs/blob/master/LICENSE)
|
||||
* [multierror](https://github.com/hashicorp/go-multierror) | Available under [license](https://github.com/hashicorp/go-multierror/blob/master/LICENSE)
|
||||
* [bcrypt](https://github.com/jameskeane/bcrypt) | Available under [license](https://github.com/jameskeane/bcrypt/blob/master/LICENSE)
|
||||
* [html2text](https://github.com/jaytaylor/html2text) | Available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE)
|
||||
* [enmime](https://github.com/jhillyerd/enmime) | Available under [license](https://github.com/jhillyerd/enmime/blob/master/LICENSE)
|
||||
* [osext](https://github.com/kardianos/osext) | Available under [license](https://github.com/kardianos/osext/blob/master/LICENSE)
|
||||
* [keychain](https://github.com/keybase/go-keychain) | Available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
|
||||
* [aurora](https://github.com/logrusorgru/aurora) | Available under [license](https://github.com/logrusorgru/aurora/blob/master/LICENSE)
|
||||
* [dns](https://github.com/miekg/dns) | Available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
|
||||
* [uuid](https://github.com/myesui/uuid) | Available under [license](https://github.com/myesui/uuid/blob/master/LICENSE)
|
||||
* [jsondiff](https://github.com/nsf/jsondiff) | Available under [license](https://github.com/nsf/jsondiff/blob/master/LICENSE)
|
||||
* [logrus](https://github.com/sirupsen/logrus) | Available under [license](https://github.com/sirupsen/logrus/blob/master/LICENSE)
|
||||
* [golang](https://github.com/skratchdot/open-golang) | Available under [license](https://github.com/skratchdot/open-golang/blob/master/LICENSE)
|
||||
* [testify](https://github.com/stretchr/testify) | Available under [license](https://github.com/stretchr/testify/blob/master/LICENSE)
|
||||
* [uuid](https://github.com/twinj/uuid) | Available under [license](https://github.com/twinj/uuid/blob/master/LICENSE)
|
||||
* [cli](https://github.com/urfave/cli) | Available under [license](https://github.com/urfave/cli/blob/master/LICENSE)
|
||||
|
||||
* [BBolt](https://pkg.go.dev/go.etcd.io/bbolt/?tab=doc) | Available under [license](https://pkg.go.dev/go.etcd.io/bbolt?tab=licenses#LICENSE)
|
||||
* [testify.v1](https://gopkg.in/stretchr/testify.v1) | Available under [license](https://github.com/stretchr/testify/blob/master/LICENSE)
|
||||
138
COPYING_NOTES.md
Normal file
@ -0,0 +1,138 @@
|
||||
# Copying
|
||||
Copyright (c) 2022 Proton AG
|
||||
|
||||
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.
|
||||
|
||||
|
||||
# Dependencies
|
||||
Proton Mail Bridge includes the following 3rd party software:
|
||||
|
||||
* [The Go Project libraries](https://golang.org/project/) | Available under [BSD license](https://golang.org/LICENSE)
|
||||
* [Qt](https://www.qt.io/) | Available under [multiple licences](https://www.qt.io/licensing)
|
||||
|
||||
<!-- START AUTOGEN -->
|
||||
* [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)
|
||||
* [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-proton-api](https://github.com/ProtonMail/go-proton-api) available under [license](https://github.com/ProtonMail/go-proton-api/blob/master/LICENSE)
|
||||
* [go-rfc5322](https://github.com/ProtonMail/go-rfc5322) available under [license](https://github.com/ProtonMail/go-rfc5322/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)
|
||||
* [ishell](https://github.com/abiosoft/ishell) available under [license](https://github.com/abiosoft/ishell/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)
|
||||
* [juniper](https://github.com/bradenaw/juniper) available under [license](https://github.com/bradenaw/juniper/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)
|
||||
* [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-imap](https://github.com/emersion/go-imap) available under [license](https://github.com/emersion/go-imap/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-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-smtp](https://github.com/emersion/go-smtp) available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE)
|
||||
* [color](https://github.com/fatih/color) available under [license](https://github.com/fatih/color/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)
|
||||
* [go-json](https://github.com/goccy/go-json) available under [license](https://github.com/goccy/go-json/blob/master/LICENSE)
|
||||
* [dbus](https://github.com/godbus/dbus) available under [license](https://github.com/godbus/dbus/blob/master/LICENSE)
|
||||
* [mock](https://github.com/golang/mock) available under [license](https://github.com/golang/mock/blob/master/LICENSE)
|
||||
* [go-cmp](https://github.com/google/go-cmp) available under [license](https://github.com/google/go-cmp/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)
|
||||
* [html2text](https://github.com/jaytaylor/html2text) available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE)
|
||||
* [go-keychain](https://github.com/keybase/go-keychain) available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
|
||||
* [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
|
||||
* [errors](https://github.com/pkg/errors) available under [license](https://github.com/pkg/errors/blob/master/LICENSE)
|
||||
* [profile](https://github.com/pkg/profile) available under [license](https://github.com/pkg/profile/blob/master/LICENSE)
|
||||
* [logrus](https://github.com/sirupsen/logrus) available under [license](https://github.com/sirupsen/logrus/blob/master/LICENSE)
|
||||
* [testify](https://github.com/stretchr/testify) available under [license](https://github.com/stretchr/testify/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)
|
||||
* [goleak](https://go.uber.org/goleak)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [atlas](https://ariga.io/atlas)
|
||||
* [ent](https://entgo.io/ent)
|
||||
* [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)
|
||||
* [levenshtein](https://github.com/agext/levenshtein) available under [license](https://github.com/agext/levenshtein/blob/master/LICENSE)
|
||||
* [cascadia](https://github.com/andybalholm/cascadia) available under [license](https://github.com/andybalholm/cascadia/blob/master/LICENSE)
|
||||
* [antlr](https://github.com/antlr/antlr4/runtime/Go/antlr) available under [license](https://github.com/antlr/antlr4/runtime/Go/antlr/blob/master/LICENSE)
|
||||
* [go-textseg](https://github.com/apparentlymart/go-textseg/v13) available under [license](https://github.com/apparentlymart/go-textseg/v13/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)
|
||||
* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/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)
|
||||
* [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)
|
||||
* [inflect](https://github.com/go-openapi/inflect) available under [license](https://github.com/go-openapi/inflect/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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [hcl](https://github.com/hashicorp/hcl/v2) available under [license](https://github.com/hashicorp/hcl/v2/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)
|
||||
* [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)
|
||||
* [go-wordwrap](https://github.com/mitchellh/go-wordwrap) available under [license](https://github.com/mitchellh/go-wordwrap/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)
|
||||
* [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)
|
||||
* [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-cty](https://github.com/zclconf/go-cty) available under [license](https://github.com/zclconf/go-cty/blob/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)
|
||||
gopkg.in/yaml.v2
|
||||
gopkg.in/yaml.v3
|
||||
* [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)
|
||||
* [go-keychain](https://github.com/cuthix/go-keychain) available under [license](https://github.com/cuthix/go-keychain/blob/master/LICENSE)
|
||||
<!-- END AUTOGEN -->
|
||||
1047
Changelog.md
399
Makefile
@ -5,141 +5,178 @@ export GO111MODULE=on
|
||||
GOOS:=$(shell go env GOOS)
|
||||
TARGET_CMD?=Desktop-Bridge
|
||||
TARGET_OS?=${GOOS}
|
||||
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||
|
||||
## Build
|
||||
.PHONY: build build-ie build-nogui build-ie-nogui check-has-go
|
||||
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
||||
|
||||
# Keep version hardcoded so app build works also without Git repository.
|
||||
BRIDGE_APP_VERSION?=1.5.0-git
|
||||
IE_APP_VERSION?=1.2.1-git
|
||||
BRIDGE_APP_VERSION?=3.0.1+git
|
||||
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_SVG:=logo.svg
|
||||
TGT_ICNS:=Bridge.icns
|
||||
ifeq "${TARGET_CMD}" "Import-Export"
|
||||
APP_VERSION:=${IE_APP_VERSION}
|
||||
SRC_ICO:=ie.ico
|
||||
SRC_ICNS:=ie.icns
|
||||
SRC_SVG:=ie.svg
|
||||
TGT_ICNS:=ImportExport.icns
|
||||
endif
|
||||
SRC_SVG:=bridge.svg
|
||||
EXE_NAME:=proton-bridge
|
||||
REVISION:=$(shell git rev-parse --short=10 HEAD)
|
||||
BUILD_TIME:=$(shell date +%FT%T%z)
|
||||
MACOS_MIN_VERSION=11.0
|
||||
|
||||
BUILD_TAGS?=pmapi_prod
|
||||
BUILD_FLAGS:=-tags='${BUILD_TAGS}'
|
||||
BUILD_FLAGS_NOGUI:=-tags='${BUILD_TAGS} nogui'
|
||||
GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/pkg/constants.,Version=${APP_VERSION} Revision=${REVISION} BuildTime=${BUILD_TIME})
|
||||
ifneq "${BUILD_LDFLAGS}" ""
|
||||
GO_LDFLAGS+= ${BUILD_LDFLAGS}
|
||||
endif
|
||||
GO_LDFLAGS:=-ldflags '${GO_LDFLAGS}'
|
||||
BUILD_FLAGS+= ${GO_LDFLAGS}
|
||||
BUILD_FLAGS_NOGUI+= ${GO_LDFLAGS}
|
||||
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} 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}
|
||||
endif
|
||||
GO_LDFLAGS_LAUNCHER:=${GO_LDFLAGS}
|
||||
ifeq "${TARGET_OS}" "windows"
|
||||
#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
|
||||
|
||||
BUILD_FLAGS+=-ldflags '${GO_LDFLAGS}'
|
||||
BUILD_FLAGS_GUI+=-ldflags "${GO_LDFLAGS}"
|
||||
BUILD_FLAGS_LAUNCHER+=-ldflags '${GO_LDFLAGS_LAUNCHER}'
|
||||
DEPLOY_DIR:=cmd/${TARGET_CMD}/deploy
|
||||
ICO_FILES:=
|
||||
EXE:=$(shell basename ${CURDIR})
|
||||
DIRNAME:=$(shell basename ${CURDIR})
|
||||
|
||||
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"
|
||||
EXE:=${EXE}.exe
|
||||
ICO_FILES:=${SRC_ICO} icon.rc icon_windows.syso
|
||||
BRIDGE_EXE:=${BRIDGE_EXE}.exe
|
||||
BRIDGE_GUI_EXE:=${BRIDGE_GUI_EXE}.exe
|
||||
LAUNCHER_EXE:=${LAUNCHER_EXE}.exe
|
||||
RESOURCE_FILE:=resource.syso
|
||||
endif
|
||||
ifeq "${TARGET_OS}" "darwin"
|
||||
DARWINAPP_CONTENTS:=${DEPLOY_DIR}/darwin/${EXE}.app/Contents
|
||||
EXE:=${EXE}.app/Contents/MacOS/${EXE}
|
||||
BRIDGE_EXE_NAME:=${BRIDGE_EXE}
|
||||
BRIDGE_EXE:=${BRIDGE_EXE}.app
|
||||
BRIDGE_GUI_EXE:=${BRIDGE_GUI_EXE}.app
|
||||
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
|
||||
EXE_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE}
|
||||
EXE_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${BRIDGE_EXE}
|
||||
EXE_GUI_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${BRIDGE_GUI_EXE}
|
||||
|
||||
TGZ_TARGET:=bridge_${TARGET_OS}_${REVISION}.tgz
|
||||
ifeq "${TARGET_CMD}" "Import-Export"
|
||||
TGZ_TARGET:=ie_${TARGET_OS}_${REVISION}.tgz
|
||||
|
||||
ifdef QT_API
|
||||
VENDOR_TARGET:=prepare-vendor update-qt-docs
|
||||
else
|
||||
VENDOR_TARGET=update-vendor
|
||||
endif
|
||||
|
||||
build: ${TGZ_TARGET}
|
||||
build-ie:
|
||||
TARGET_CMD=Import-Export $(MAKE) build
|
||||
build: build-gui
|
||||
|
||||
build-nogui:
|
||||
go build ${BUILD_FLAGS_NOGUI} -o ${TARGET_CMD} cmd/${TARGET_CMD}/main.go
|
||||
build-gui: ${TGZ_TARGET}
|
||||
|
||||
build-ie-nogui:
|
||||
TARGET_CMD=Import-Export $(MAKE) build-nogui
|
||||
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} CGO_ENABLED=1 CGO_CFLAGS="-mmacosx-version-min=${MACOS_MIN_VERSION}" GOARCH=arm64 $(call go-build,$(1),$(2)_arm,$(3)) && \
|
||||
MACOSX_DEPLOYMENT_TARGET=${MACOS_MIN_VERSION} CGO_ENABLED=1 CGO_CFLAGS="-mmacosx-version-min=${MACOS_MIN_VERSION}" 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"
|
||||
go-build-finalize= \
|
||||
powershell Copy-Item ${ROOT_DIR}/${RESOURCE_FILE} ${4} && \
|
||||
$(call go-build,$(1),$(2),$(3)) && \
|
||||
powershell Remove-Item ${4} -Force
|
||||
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}
|
||||
$(call go-build-finalize,${BUILD_FLAGS_LAUNCHER},"${LAUNCHER_EXE}","${ROOT_DIR}/${LAUNCHER_PATH}/","${ROOT_DIR}/${LAUNCHER_PATH}/${RESOURCE_FILE}")
|
||||
|
||||
versioner:
|
||||
go build ${BUILD_FLAGS} -o versioner utils/versioner/main.go
|
||||
|
||||
vault-editor:
|
||||
go build -tags debug -o vault-editor utils/vault-editor/main.go
|
||||
|
||||
hasher:
|
||||
go build -o hasher utils/hasher/main.go
|
||||
|
||||
${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS}
|
||||
rm -f $@
|
||||
cd ${DEPLOY_DIR} && tar czf ../../../$@ ${TARGET_OS}
|
||||
tar -czvf $@ -C ${DEPLOY_DIR}/${TARGET_OS} .
|
||||
|
||||
${DEPLOY_DIR}/linux: ${EXE_TARGET}
|
||||
cp -pf ./internal/frontend/share/icons/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
|
||||
${DEPLOY_DIR}/linux: ${EXE_TARGET} build-launcher
|
||||
cp -pf ./dist/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
|
||||
cp -pf ./LICENSE ${DEPLOY_DIR}/linux/
|
||||
cp -pf ./Changelog.md ${DEPLOY_DIR}/linux/
|
||||
cp -pf ./dist/${EXE_NAME}.desktop ${DEPLOY_DIR}/linux/
|
||||
mv ${LAUNCHER_EXE} ${DEPLOY_DIR}/linux/
|
||||
|
||||
${DEPLOY_DIR}/darwin: ${EXE_TARGET}
|
||||
cp ./internal/frontend/share/icons/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${TGT_ICNS}
|
||||
${DEPLOY_DIR}/darwin: ${EXE_TARGET} build-launcher
|
||||
mv ${EXE_GUI_TARGET} ${EXE_TARGET_DARWIN}
|
||||
mv ${EXE_TARGET} ${DARWINAPP_CONTENTS}/MacOS/${BRIDGE_EXE_NAME}
|
||||
perl -i -pe"s/>${BRIDGE_GUI_EXE_NAME}/>${LAUNCHER_EXE}/g" ${DARWINAPP_CONTENTS}/Info.plist
|
||||
cp ./dist/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${SRC_ICNS}
|
||||
cp LICENSE ${DARWINAPP_CONTENTS}/Resources/
|
||||
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngine.framework"
|
||||
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebView.framework"
|
||||
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngineCore.framework"
|
||||
./utils/remove_non_relative_links_darwin.sh "${EXE_TARGET}"
|
||||
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}
|
||||
cp ./internal/frontend/share/icons/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
|
||||
cp LICENSE ${DEPLOY_DIR}/windows/
|
||||
${DEPLOY_DIR}/windows: ${EXE_TARGET} build-launcher
|
||||
cp ./dist/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
|
||||
cp LICENSE ${DEPLOY_DIR}/windows/LICENSE.txt
|
||||
mv ${LAUNCHER_EXE} ${DEPLOY_DIR}/windows/$(notdir ${LAUNCHER_EXE})
|
||||
# plugins are installed in a plugins folder while needs to be near the exe
|
||||
cp -rf ${DEPLOY_DIR}/windows/plugins/* ${DEPLOY_DIR}/windows/.
|
||||
rm -rf ${DEPLOY_DIR}/windows/plugins
|
||||
|
||||
QT_BUILD_TARGET:=build desktop
|
||||
ifneq "${GOOS}" "${TARGET_OS}"
|
||||
ifeq "${TARGET_OS}" "windows"
|
||||
QT_BUILD_TARGET:=-docker build windows_64_shared
|
||||
endif
|
||||
endif
|
||||
|
||||
${EXE_TARGET}: check-has-go gofiles ${ICO_FILES} update-vendor
|
||||
rm -rf deploy ${TARGET_OS} ${DEPLOY_DIR}
|
||||
cp cmd/${TARGET_CMD}/main.go .
|
||||
qtdeploy ${BUILD_FLAGS} ${QT_BUILD_TARGET}
|
||||
mv deploy cmd/${TARGET_CMD}
|
||||
rm -rf ${TARGET_OS} main.go
|
||||
|
||||
logo.ico ie.ico: ./internal/frontend/share/icons/${SRC_ICO}
|
||||
cp $^ $@
|
||||
icon.rc: ./internal/frontend/share/icon.rc
|
||||
cp $^ .
|
||||
icon_windows.syso: icon.rc logo.ico
|
||||
windres --target=pe-x86-64 -o $@ $<
|
||||
|
||||
|
||||
## Rules for therecipe/qt
|
||||
.PHONY: prepare-vendor update-vendor
|
||||
THERECIPE_ENV:=github.com/therecipe/env_${TARGET_OS}_amd64_513
|
||||
|
||||
# vendor folder will be deleted by gomod hence we cache the big repo
|
||||
# therecipe/env in order to download it only once
|
||||
vendor-cache/${THERECIPE_ENV}:
|
||||
git clone https://${THERECIPE_ENV}.git vendor-cache/${THERECIPE_ENV}
|
||||
|
||||
# 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}
|
||||
${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_BUILD_TIME=${BUILD_TIME} \
|
||||
BRIDGE_GUI_BUILD_CONFIG=Release \
|
||||
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)
|
||||
APP_VERSION_COMMA:=$(shell echo "${APP_VERSION}" | sed -e 's/[^0-9,.]*//g' -e 's/\./,/g')
|
||||
${RESOURCE_FILE}: ./dist/info.rc ./dist/${SRC_ICO} .FORCE
|
||||
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 ./${RESOURCE_FILE} $<
|
||||
|
||||
## Dev dependencies
|
||||
.PHONY: install-devel-tools install-linter install-go-mod-outdated
|
||||
LINTVER:="v1.29.0"
|
||||
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
|
||||
LINTVER:="v1.50.0"
|
||||
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
||||
|
||||
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
|
||||
@ -153,13 +190,29 @@ install-linter: check-has-go
|
||||
curl -sfL $(LINTSRC) | sh -s -- -b $(shell go env GOPATH)/bin $(LINTVER)
|
||||
|
||||
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:
|
||||
cp utils/githooks/* .git/hooks/
|
||||
chmod +x .git/hooks/*
|
||||
|
||||
## Checks, mocks and docs
|
||||
.PHONY: check-has-go add-license change-copyright-year test bench coverage mocks lint-license lint-golang lint updates doc
|
||||
.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:
|
||||
@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:
|
||||
./utils/missing_license.sh add
|
||||
@ -168,23 +221,19 @@ change-copyright-year:
|
||||
./utils/missing_license.sh change-year
|
||||
|
||||
test: gofiles
|
||||
@# Listing packages manually to not run Qt folder (which needs to run qtsetup first) and integration tests.
|
||||
go test -coverprofile=/tmp/coverage.out -run=${TESTRUN} \
|
||||
./internal/api/... \
|
||||
./internal/bridge/... \
|
||||
./internal/events/... \
|
||||
./internal/frontend/autoconfig/... \
|
||||
./internal/frontend/cli/... \
|
||||
./internal/imap/... \
|
||||
./internal/metrics/... \
|
||||
./internal/importexport/... \
|
||||
./internal/preferences/... \
|
||||
./internal/smtp/... \
|
||||
./internal/store/... \
|
||||
./internal/transfer/... \
|
||||
./internal/updates/... \
|
||||
./internal/users/... \
|
||||
./pkg/...
|
||||
go test -v -timeout=5m -p=1 -count=1 -coverprofile=/tmp/coverage.out -run=${TESTRUN} ./internal/... ./pkg/...
|
||||
|
||||
test-race: gofiles
|
||||
go test -v -timeout=30m -p=1 -count=1 -race -failfast -run=${TESTRUN} ./internal/... ./pkg/...
|
||||
|
||||
test-integration: gofiles
|
||||
go test -v -timeout=10m -p=1 -count=1 github.com/ProtonMail/proton-bridge/v3/tests
|
||||
|
||||
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
|
||||
|
||||
bench:
|
||||
go test -run '^$$' -bench=. -memprofile bench_mem.pprof -cpuprofile bench_cpu.pprof ./internal/store
|
||||
@ -195,21 +244,35 @@ coverage: test
|
||||
go tool cover -html=/tmp/coverage.out -o=coverage.html
|
||||
|
||||
mocks:
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/users Configer,PanicHandler,ClientManager,CredentialsStorer,StoreMaker > internal/users/mocks/mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/transfer PanicHandler,ClientManager > internal/transfer/mocks/mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,ClientManager,BridgeUser > internal/store/mocks/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/proton-bridge/pkg/pmapi Client > pkg/pmapi/mocks/mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/bridge TLSReporter,ProxyController,Autostarter > tmp
|
||||
mv tmp internal/bridge/mocks/mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/async PanicHandler > internal/bridge/mocks/async_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/v3/internal/updater Downloader,Installer > internal/updater/mocks/mocks.go
|
||||
|
||||
lint: lint-golang lint-license
|
||||
lint: gofiles lint-golang lint-license lint-dependencies lint-changelog
|
||||
|
||||
lint-license:
|
||||
./utils/missing_license.sh check
|
||||
|
||||
lint-dependencies:
|
||||
./utils/dependency_license.sh check
|
||||
|
||||
lint-changelog:
|
||||
./utils/changelog_linter.sh Changelog.md
|
||||
|
||||
lint-golang:
|
||||
which golangci-lint || $(MAKE) install-linter
|
||||
$(info linting with GOMAXPROCS=${GOMAXPROCS})
|
||||
golangci-lint run ./...
|
||||
|
||||
gobinsec: gobinsec-cache.yml build
|
||||
gobinsec -wait -cache -config utils/gobinsec_conf.yml ${EXE_TARGET} ${DEPLOY_DIR}/${TARGET_OS}/${LAUNCHER_EXE}
|
||||
|
||||
gobinsec-cache.yml:
|
||||
./utils/gobinsec_update.sh
|
||||
cp ./utils/gobinsec_update/gobinsec-cache-valid.yml ./gobinsec-cache.yml
|
||||
|
||||
updates: install-go-mod-outdated
|
||||
# Uncomment the "-ci" to fail the job if something can be updated.
|
||||
go list -u -m -json all | go-mod-outdated -update -direct #-ci
|
||||
@ -217,66 +280,74 @@ updates: install-go-mod-outdated
|
||||
doc:
|
||||
godoc -http=:6060
|
||||
|
||||
release-notes: release-notes/bridge_stable.html release-notes/bridge_early.html
|
||||
|
||||
release-notes/%.html: release-notes/%.md
|
||||
./utils/release_notes.sh $^
|
||||
|
||||
.PHONY: gofiles
|
||||
# Following files are for the whole app so it makes sense to have them in bridge package.
|
||||
# (Options like cmd or internal were considered and bridge package is the best place for them.)
|
||||
gofiles: ./internal/bridge/credits.go ./internal/bridge/release_notes.go ./internal/importexport/credits.go ./internal/importexport/release_notes.go
|
||||
gofiles: ./internal/bridge/credits.go
|
||||
./internal/bridge/credits.go: ./utils/credits.sh go.mod
|
||||
cd ./utils/ && ./credits.sh bridge
|
||||
./internal/bridge/release_notes.go: ./utils/release-notes.sh ./release-notes/notes-bridge.txt ./release-notes/bugs-bridge.txt
|
||||
cd ./utils/ && ./release-notes.sh bridge
|
||||
./internal/importexport/credits.go: ./utils/credits.sh go.mod
|
||||
cd ./utils/ && ./credits.sh importexport
|
||||
./internal/importexport/release_notes.go: ./utils/release-notes.sh ./release-notes/notes-importexport.txt ./release-notes/bugs-importexport.txt
|
||||
cd ./utils/ && ./release-notes.sh importexport
|
||||
|
||||
|
||||
## Run and debug
|
||||
.PHONY: run run-qt run-qt-cli run-nogui run-nogui-cli run-debug run-qml-preview run-ie-qml-preview run-ie run-ie-qt run-ie-qt-cli run-ie-nogui run-ie-nogui-cli clean-vendor clean-frontend-qt clean-frontend-qt-ie clean-frontend-qt-common clean
|
||||
.PHONY: run run-qt run-qt-cli run-nogui run-cli run-noninteractive run-debug run-qml-preview clean-vendor clean-frontend-qt clean-frontend-qt-common clean
|
||||
|
||||
VERBOSITY?=debug-client
|
||||
RUN_FLAGS:=-m -l=${VERBOSITY}
|
||||
LOG?=debug
|
||||
LOG_IMAP?=client # client/server/all, or empty to turn it off
|
||||
LOG_SMTP?=--log-smtp # empty to turn it off
|
||||
RUN_FLAGS?=-l=${LOG} --log-imap=${LOG_IMAP} ${LOG_SMTP}
|
||||
|
||||
run: run-nogui-cli
|
||||
run: run-qt
|
||||
|
||||
run-qt: ${EXE_TARGET}
|
||||
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} | tee last.log
|
||||
run-qt-cli: ${EXE_TARGET}
|
||||
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} -c
|
||||
run-cli: run-nogui
|
||||
|
||||
run-nogui: clean-vendor gofiles
|
||||
PROTONMAIL_ENV=dev go run ${BUILD_FLAGS_NOGUI} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} | tee last.log
|
||||
run-nogui-cli: clean-vendor gofiles
|
||||
PROTONMAIL_ENV=dev go run ${BUILD_FLAGS_NOGUI} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} -c
|
||||
run-noninteractive: build-nogui clean-vendor gofiles
|
||||
PROTONMAIL_ENV=dev ./${LAUNCHER_EXE} ${RUN_FLAGS} -n
|
||||
|
||||
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:
|
||||
PROTONMAIL_ENV=dev dlv debug --build-flags "${BUILD_FLAGS_NOGUI}" cmd/${TARGET_CMD}/main.go -- ${RUN_FLAGS}
|
||||
dlv debug ./cmd/Desktop-Bridge/main.go -- -l=debug
|
||||
|
||||
run-qml-preview:
|
||||
$(MAKE) -C internal/frontend/qt -f Makefile.local qmlpreview
|
||||
run-ie-qml-preview:
|
||||
$(MAKE) -C internal/frontend/qt-ie -f Makefile.local qmlpreview
|
||||
|
||||
run-ie:
|
||||
TARGET_CMD=Import-Export $(MAKE) run
|
||||
run-ie-qt:
|
||||
TARGET_CMD=Import-Export $(MAKE) run-qt
|
||||
run-ie-nogui:
|
||||
TARGET_CMD=Import-Export $(MAKE) run-nogui
|
||||
|
||||
clean-frontend-qt:
|
||||
$(MAKE) -C internal/frontend/qt -f Makefile.local clean
|
||||
clean-frontend-qt-ie:
|
||||
$(MAKE) -C internal/frontend/qt-ie -f Makefile.local clean
|
||||
clean-frontend-qt-common:
|
||||
$(MAKE) -C internal/frontend/qt-common -f Makefile.local clean
|
||||
|
||||
clean-vendor: clean-frontend-qt clean-frontend-qt-ie clean-frontend-qt-common
|
||||
clean-vendor:
|
||||
rm -rf ./vendor
|
||||
|
||||
clean: clean-vendor
|
||||
clean-gui:
|
||||
cd internal/frontend/bridge-gui/ && \
|
||||
rm -f Version.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 cmd/Desktop-Bridge/deploy
|
||||
rm -rf cmd/Import-Export/deploy
|
||||
rm -f build last.log mem.pprof main.go
|
||||
rm -rf logo.ico icon.rc icon_windows.syso internal/frontend/qt/icon_windows.syso
|
||||
rm -f ./*.syso
|
||||
rm -f release-notes/bridge.html
|
||||
rm -f release-notes/import-export.html
|
||||
rm -f ${LAUNCHER_EXE} ${BRIDGE_EXE} ${BRIDGE_EXE_NAME}
|
||||
|
||||
|
||||
.PHONY: generate
|
||||
generate:
|
||||
go generate ./...
|
||||
$(MAKE) add-license
|
||||
|
||||
.FORCE:
|
||||
|
||||
54
README.md
@ -1,48 +1,49 @@
|
||||
# ProtonMail Bridge and Import Export app
|
||||
Copyright (c) 2020 Proton Technologies AG
|
||||
# Proton Mail Bridge and Import Export app
|
||||
Copyright (c) 2022 Proton AG
|
||||
|
||||
This repository holds the ProtonMail Bridge and the ProtonMail 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 licensing information see [COPYING](./COPYING.md).
|
||||
The license can be found in [LICENSE](./LICENSE) file, for more licensing information see [COPYING_NOTES](./COPYING_NOTES.md).
|
||||
For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md).
|
||||
|
||||
|
||||
## Description Bridge
|
||||
ProtonMail Bridge for e-mail clients.
|
||||
Proton Mail Bridge for e-mail clients.
|
||||
|
||||
When launched, Bridge will initialize local IMAP/SMTP servers and render
|
||||
its GUI.
|
||||
|
||||
To configure an e-mail client, firstly log in using your ProtonMail credentials.
|
||||
To configure an e-mail client, firstly log in using your Proton Mail credentials.
|
||||
Open your e-mail client and add a new account using the settings which are
|
||||
located in the Bridge GUI. The client will only be able to sync with
|
||||
your ProtonMail account when the Bridge is running, thus the option
|
||||
your Proton Mail account when the Bridge is running, thus the option
|
||||
to start Bridge on startup is enabled by default.
|
||||
|
||||
When the main window is closed, Bridge will continue to run in the
|
||||
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
|
||||
ProtonMail Import-Export app for importing and exporting messages.
|
||||
## Launchers
|
||||
Launchers are binaries used to run the Proton Mail Bridge or Import-Export apps.
|
||||
|
||||
To transfer messages, firstly log in using your ProtonMail 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).
|
||||
Official distributions of the Proton Mail Bridge and Import-Export apps contain
|
||||
both a launcher and the app itself. The launcher is installed in a protected
|
||||
area of the system (i.e. an area accessible only with admin privileges) and is
|
||||
used to run the app. The launcher ensures that nobody tampered with the app's
|
||||
files by verifying their signature using a hardcoded public key. App files are
|
||||
placed in regular userspace and are signed by Proton's private key. This
|
||||
feature enables the app to securely update itself automatically without asking
|
||||
the user for a password.
|
||||
|
||||
## Keychain
|
||||
You need to have a keychain in order to run the ProtonMail Bridge. On Mac or
|
||||
Windows, Bridge uses native credential managers. On Linux, use
|
||||
[Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/)
|
||||
You need to have a keychain in order to run the Proton Mail Bridge. On Mac or
|
||||
Windows, Bridge uses native credential managers. On Linux, use `secret-service` freedesktop.org API
|
||||
(e.g. [Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/))
|
||||
or
|
||||
[pass](https://www.passwordstore.org/).
|
||||
[pass](https://www.passwordstore.org/). We are working on allowing other secret
|
||||
services (e.g. KeepassXC), but for now only gnome-keyring is usable without
|
||||
major problems.
|
||||
|
||||
|
||||
## Environment Variables
|
||||
@ -57,7 +58,6 @@ or
|
||||
|
||||
### Integration testing
|
||||
- `TEST_ENV`: set which env to use (fake or live)
|
||||
- `TEST_APP`: set which app to test (bridge or ie)
|
||||
- `TEST_ACCOUNTS`: set JSON file with configured accounts
|
||||
- `TAGS`: set build tags for tests
|
||||
- `FEATURES`: set feature dir, file or scenario to test
|
||||
@ -72,9 +72,9 @@ The database stores metadata necessary for presenting messages and mailboxes to
|
||||
|
||||
### Preferences
|
||||
User preferences are stored in json at the following location:
|
||||
- Linux: `~/.cache/protonmail/bridge/<cacheVersion>/prefs.json` (unless `XDG_CACHE_HOME` is set, in which case that is used as your `~`)
|
||||
- macOS: `~/Library/Caches/protonmail/bridge/<cacheVersion>/prefs.json`
|
||||
- Windows: `%LOCALAPPDATA%\protonmail\bridge\<cacheVersion>\prefs.json`
|
||||
- Linux: `~/.config/protonmail/bridge/prefs.json`
|
||||
- macOS: `~/Library/ApplicationSupport/protonmail/bridge/prefs.json`
|
||||
- Windows: `%APPDATA%\protonmail\bridge\prefs.json`
|
||||
|
||||
### IMAP Cache
|
||||
The currently subscribed mailboxes are held in a json file:
|
||||
|
||||
1
TODO.md
Normal file
@ -0,0 +1 @@
|
||||
- when cache is full, we need to stop the watcher? don't want to keep downloading messages and throwing them away when we try to cache them.
|
||||
@ -1,22 +1,31 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// 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.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/app"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
/*
|
||||
___....___
|
||||
^^ __..-:'':__:..:__:'':-..__
|
||||
@ -34,262 +43,8 @@ package main
|
||||
~~^_~^~/ \~^-~^~ _~^-~_^~-^~_^~~-^~_~^~-~_~-^~_^/ \~^ ~~_ ^
|
||||
*/
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/api"
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/cmd"
|
||||
"github.com/ProtonMail/proton-bridge/internal/cookies"
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
||||
"github.com/ProtonMail/proton-bridge/internal/imap"
|
||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||
"github.com/ProtonMail/proton-bridge/internal/smtp"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updates"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
// cacheVersion is used for cache files such as lock, events, preferences, user_info, db files.
|
||||
// Different number will drop old files and create new ones.
|
||||
cacheVersion = "c11"
|
||||
|
||||
appName = "bridge"
|
||||
)
|
||||
|
||||
var (
|
||||
log = logrus.WithField("pkg", "main") //nolint[gochecknoglobals]
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Main(
|
||||
"ProtonMail Bridge",
|
||||
"ProtonMail IMAP and SMTP Bridge",
|
||||
[]cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "no-window",
|
||||
Usage: "Don't show window after start"},
|
||||
cli.BoolFlag{
|
||||
Name: "noninteractive",
|
||||
Usage: "Start Bridge entirely noninteractively"},
|
||||
},
|
||||
run,
|
||||
)
|
||||
}
|
||||
|
||||
// run initializes and starts everything in a precise order.
|
||||
//
|
||||
// IMPORTANT: ***Read the comments before CHANGING the order ***
|
||||
func run(context *cli.Context) (contextError error) { // nolint[funlen]
|
||||
// We need to have config instance to setup a logs, panic handler, etc ...
|
||||
cfg := config.New(appName, constants.Version, constants.Revision, cacheVersion)
|
||||
|
||||
// We want to know about any problem. Our PanicHandler calls sentry which is
|
||||
// not dependent on anything else. If that fails, it tries to create crash
|
||||
// report which will not be possible if no folder can be created. That's the
|
||||
// only problem we will not be notified about in any way.
|
||||
panicHandler := &cmd.PanicHandler{
|
||||
AppName: "ProtonMail Bridge",
|
||||
Config: cfg,
|
||||
Err: &contextError,
|
||||
}
|
||||
defer panicHandler.HandlePanic()
|
||||
|
||||
// First we need config and create necessary folder; it's dependency for everything.
|
||||
if err := cfg.CreateDirs(); err != nil {
|
||||
log.Fatal("Cannot create necessary folders: ", err)
|
||||
}
|
||||
|
||||
// Setup of logs should be as soon as possible to ensure we record every wanted report in the log.
|
||||
logLevel := context.GlobalString("log-level")
|
||||
debugClient, debugServer := config.SetupLog(cfg, logLevel)
|
||||
|
||||
// Doesn't make sense to continue when Bridge was invoked with wrong arguments.
|
||||
// We should tell that to the user before we do anything else.
|
||||
if context.Args().First() != "" {
|
||||
_ = cli.ShowAppHelp(context)
|
||||
return cli.NewExitError("Unknown argument", 4)
|
||||
}
|
||||
|
||||
// It's safe to get version JSON file even when other instance is running.
|
||||
// (thus we put it before check of presence of other Bridge instance).
|
||||
updates := updates.NewBridge(cfg.GetUpdateDir())
|
||||
|
||||
if dir := context.GlobalString("version-json"); dir != "" {
|
||||
cmd.GenerateVersionFiles(updates, dir)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Should be called after logs are configured but before preferences are created.
|
||||
migratePreferencesFromC10(cfg)
|
||||
|
||||
// ClearOldData before starting new bridge to do a proper setup.
|
||||
//
|
||||
// IMPORTANT: If you the change position of this you will need to wait
|
||||
// until force-update to be applied on all currently used bridge
|
||||
// versions
|
||||
if err := cfg.ClearOldData(); err != nil {
|
||||
log.Error("Cannot clear old data: ", err)
|
||||
}
|
||||
|
||||
// GetTLSConfig is needed for IMAP, SMTL and local bridge API (to check second instance).
|
||||
//
|
||||
// This should be called after ClearOldData, in order to re-create the
|
||||
// certificates if clean data will remove them (accidentally or on purpose).
|
||||
tls, err := config.GetTLSConfig(cfg)
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Cannot get TLS certificate")
|
||||
}
|
||||
|
||||
pref := preferences.New(cfg)
|
||||
|
||||
// Now we can try to proceed with starting the bridge. First we need to ensure
|
||||
// this is the only instance. If not, we will end and focus the existing one.
|
||||
lock, err := singleinstance.CreateLockFile(cfg.GetLockPath())
|
||||
if err != nil {
|
||||
log.Warn("Bridge is already running")
|
||||
if err := api.CheckOtherInstanceAndFocus(pref.GetInt(preferences.APIPortKey), tls); err != nil {
|
||||
cmd.DisableRestart()
|
||||
log.Error("Second instance: ", err)
|
||||
}
|
||||
return cli.NewExitError("Bridge is already running.", 3)
|
||||
}
|
||||
defer lock.Close() //nolint[errcheck]
|
||||
|
||||
// In case user wants to do CPU or memory profiles...
|
||||
if doCPUProfile := context.GlobalBool("cpu-prof"); doCPUProfile {
|
||||
cmd.StartCPUProfile()
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
if doMemoryProfile := context.GlobalBool("mem-prof"); doMemoryProfile {
|
||||
defer cmd.MakeMemoryProfile()
|
||||
}
|
||||
|
||||
// Now we initialize all Bridge parts.
|
||||
log.Debug("Initializing bridge...")
|
||||
eventListener := listener.New()
|
||||
events.SetupEvents(eventListener)
|
||||
|
||||
credentialsStore, credentialsError := credentials.NewStore(appName)
|
||||
if credentialsError != nil {
|
||||
log.Error("Could not get credentials store: ", credentialsError)
|
||||
}
|
||||
|
||||
cm := pmapi.NewClientManager(cfg.GetAPIConfig())
|
||||
|
||||
// Different build types have different roundtrippers (e.g. we want to enable
|
||||
// TLS fingerprint checks in production builds). GetRoundTripper has a different
|
||||
// implementation depending on whether build flag pmapi_prod is used or not.
|
||||
cm.SetRoundTripper(cfg.GetRoundTripper(cm, eventListener))
|
||||
|
||||
// Cookies must be persisted across restarts.
|
||||
jar, err := cookies.NewCookieJar(pref)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Warn("Could not create cookie jar")
|
||||
} else {
|
||||
cm.SetCookieJar(jar)
|
||||
}
|
||||
|
||||
bridgeInstance := bridge.New(cfg, pref, panicHandler, eventListener, cm, credentialsStore)
|
||||
imapBackend := imap.NewIMAPBackend(panicHandler, eventListener, cfg, bridgeInstance)
|
||||
smtpBackend := smtp.NewSMTPBackend(panicHandler, eventListener, pref, bridgeInstance)
|
||||
|
||||
go func() {
|
||||
defer panicHandler.HandlePanic()
|
||||
apiServer := api.NewAPIServer(pref, tls, cfg.GetTLSCertPath(), cfg.GetTLSKeyPath(), eventListener)
|
||||
apiServer.ListenAndServe()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer panicHandler.HandlePanic()
|
||||
imapPort := pref.GetInt(preferences.IMAPPortKey)
|
||||
imapServer := imap.NewIMAPServer(debugClient, debugServer, imapPort, tls, imapBackend, eventListener)
|
||||
imapServer.ListenAndServe()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer panicHandler.HandlePanic()
|
||||
smtpPort := pref.GetInt(preferences.SMTPPortKey)
|
||||
useSSL := pref.GetBool(preferences.SMTPSSLKey)
|
||||
smtpServer := smtp.NewSMTPServer(debugClient || debugServer, smtpPort, useSSL, tls, smtpBackend, eventListener)
|
||||
smtpServer.ListenAndServe()
|
||||
}()
|
||||
|
||||
// Decide about frontend mode before initializing rest of bridge.
|
||||
var frontendMode string
|
||||
|
||||
switch {
|
||||
case context.GlobalBool("cli"):
|
||||
frontendMode = "cli"
|
||||
case context.GlobalBool("noninteractive"):
|
||||
frontendMode = "noninteractive"
|
||||
default:
|
||||
frontendMode = "qt"
|
||||
}
|
||||
|
||||
log.WithField("mode", frontendMode).Debug("Determined frontend mode to use")
|
||||
|
||||
// If we are starting bridge in noninteractive mode, simply block instead of starting a frontend.
|
||||
if frontendMode == "noninteractive" {
|
||||
<-(make(chan struct{}))
|
||||
return nil
|
||||
}
|
||||
|
||||
showWindowOnStart := !context.GlobalBool("no-window")
|
||||
frontend := frontend.New(constants.Version, constants.BuildVersion, frontendMode, showWindowOnStart, panicHandler, cfg, pref, eventListener, updates, bridgeInstance, smtpBackend)
|
||||
|
||||
// Last part is to start everything.
|
||||
log.Debug("Starting frontend...")
|
||||
if err := frontend.Loop(credentialsError); err != nil {
|
||||
log.Error("Frontend failed with error: ", err)
|
||||
return cli.NewExitError("Frontend error", 2)
|
||||
}
|
||||
|
||||
if frontend.IsAppRestarting() {
|
||||
cmd.RestartApp()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migratePreferencesFromC10 will copy preferences from c10 folder to c11.
|
||||
// It will happen only when c10/prefs.json exists and c11/prefs.json not.
|
||||
// No configuration changed between c10 and c11 versions.
|
||||
func migratePreferencesFromC10(cfg *config.Config) {
|
||||
pref10Path := config.New(appName, constants.Version, constants.Revision, "c10").GetPreferencesPath()
|
||||
if _, err := os.Stat(pref10Path); os.IsNotExist(err) {
|
||||
log.WithField("path", pref10Path).Trace("Old preferences does not exist, migration skipped")
|
||||
return
|
||||
}
|
||||
|
||||
pref11Path := cfg.GetPreferencesPath()
|
||||
if _, err := os.Stat(pref11Path); err == nil {
|
||||
log.WithField("path", pref11Path).Trace("New preferences already exists, migration skipped")
|
||||
return
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(pref10Path) //nolint[gosec]
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Problem to load old preferences")
|
||||
return
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(pref11Path, data, 0600)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Problem to migrate preferences")
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("Preferences migrated")
|
||||
if err := app.New().Run(xslices.Filter(os.Args, func(arg string) bool { return !strings.Contains(arg, "-psn_") })); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,177 +0,0 @@
|
||||
// Copyright (c) 2020 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/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime/pprof"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/cmd"
|
||||
"github.com/ProtonMail/proton-bridge/internal/cookies"
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updates"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
// cacheVersion is used for cache files such as lock, or preferences.
|
||||
// Different number will drop old files and create new ones.
|
||||
cacheVersion = "c11"
|
||||
|
||||
appName = "importExport"
|
||||
appNameDash = "import-export-app"
|
||||
)
|
||||
|
||||
var (
|
||||
log = logrus.WithField("pkg", "main") //nolint[gochecknoglobals]
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Main(
|
||||
"ProtonMail Import-Export",
|
||||
"ProtonMail Import-Export app",
|
||||
nil,
|
||||
run,
|
||||
)
|
||||
}
|
||||
|
||||
// run initializes and starts everything in a precise order.
|
||||
//
|
||||
// IMPORTANT: ***Read the comments before CHANGING the order ***
|
||||
func run(context *cli.Context) (contextError error) { // nolint[funlen]
|
||||
// We need to have config instance to setup a logs, panic handler, etc ...
|
||||
cfg := config.New(appName, constants.Version, constants.Revision, cacheVersion)
|
||||
|
||||
// We want to know about any problem. Our PanicHandler calls sentry which is
|
||||
// not dependent on anything else. If that fails, it tries to create crash
|
||||
// report which will not be possible if no folder can be created. That's the
|
||||
// only problem we will not be notified about in any way.
|
||||
panicHandler := &cmd.PanicHandler{
|
||||
AppName: "ProtonMail Import-Export app",
|
||||
Config: cfg,
|
||||
Err: &contextError,
|
||||
}
|
||||
defer panicHandler.HandlePanic()
|
||||
|
||||
// First we need config and create necessary folder; it's dependency for everything.
|
||||
if err := cfg.CreateDirs(); err != nil {
|
||||
log.Fatal("Cannot create necessary folders: ", err)
|
||||
}
|
||||
|
||||
// Setup of logs should be as soon as possible to ensure we record every wanted report in the log.
|
||||
logLevel := context.GlobalString("log-level")
|
||||
_, _ = config.SetupLog(cfg, logLevel)
|
||||
|
||||
// Doesn't make sense to continue when Import-Export was invoked with wrong arguments.
|
||||
// We should tell that to the user before we do anything else.
|
||||
if context.Args().First() != "" {
|
||||
_ = cli.ShowAppHelp(context)
|
||||
return cli.NewExitError("Unknown argument", 4)
|
||||
}
|
||||
|
||||
// It's safe to get version JSON file even when other instance is running.
|
||||
// (thus we put it before check of presence of other Import-Export instance).
|
||||
updates := updates.NewImportExport(cfg.GetUpdateDir())
|
||||
|
||||
if dir := context.GlobalString("version-json"); dir != "" {
|
||||
cmd.GenerateVersionFiles(updates, dir)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Now we can try to proceed with starting the Import-Export. First we need to ensure
|
||||
// this is the only instance. If not, we will end and focus the existing one.
|
||||
lock, err := singleinstance.CreateLockFile(cfg.GetLockPath())
|
||||
if err != nil {
|
||||
log.Warn("Import-Export app is already running")
|
||||
return cli.NewExitError("Import-Export app is already running.", 3)
|
||||
}
|
||||
defer lock.Close() //nolint[errcheck]
|
||||
|
||||
// In case user wants to do CPU or memory profiles...
|
||||
if doCPUProfile := context.GlobalBool("cpu-prof"); doCPUProfile {
|
||||
cmd.StartCPUProfile()
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
if doMemoryProfile := context.GlobalBool("mem-prof"); doMemoryProfile {
|
||||
defer cmd.MakeMemoryProfile()
|
||||
}
|
||||
|
||||
// Now we initialize all Import-Export parts.
|
||||
log.Debug("Initializing import-export...")
|
||||
eventListener := listener.New()
|
||||
events.SetupEvents(eventListener)
|
||||
|
||||
credentialsStore, credentialsError := credentials.NewStore(appNameDash)
|
||||
if credentialsError != nil {
|
||||
log.Error("Could not get credentials store: ", credentialsError)
|
||||
}
|
||||
|
||||
cm := pmapi.NewClientManager(cfg.GetAPIConfig())
|
||||
|
||||
// Different build types have different roundtrippers (e.g. we want to enable
|
||||
// TLS fingerprint checks in production builds). GetRoundTripper has a different
|
||||
// implementation depending on whether build flag pmapi_prod is used or not.
|
||||
cm.SetRoundTripper(cfg.GetRoundTripper(cm, eventListener))
|
||||
|
||||
pref := preferences.New(cfg)
|
||||
|
||||
// Cookies must be persisted across restarts.
|
||||
jar, err := cookies.NewCookieJar(pref)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Warn("Could not create cookie jar")
|
||||
} else {
|
||||
cm.SetCookieJar(jar)
|
||||
}
|
||||
|
||||
importexportInstance := importexport.New(cfg, panicHandler, eventListener, cm, credentialsStore)
|
||||
|
||||
// Decide about frontend mode before initializing rest of import-export.
|
||||
var frontendMode string
|
||||
switch {
|
||||
case context.GlobalBool("cli"):
|
||||
frontendMode = "cli"
|
||||
default:
|
||||
frontendMode = "qt"
|
||||
}
|
||||
log.WithField("mode", frontendMode).Debug("Determined frontend mode to use")
|
||||
|
||||
frontend := frontend.NewImportExport(constants.Version, constants.BuildVersion, frontendMode, panicHandler, cfg, eventListener, updates, importexportInstance)
|
||||
|
||||
// Last part is to start everything.
|
||||
log.Debug("Starting frontend...")
|
||||
if err := frontend.Loop(credentialsError); err != nil {
|
||||
log.Error("Frontend failed with error: ", err)
|
||||
return cli.NewExitError("Frontend error", 2)
|
||||
}
|
||||
|
||||
if frontend.IsAppRestarting() {
|
||||
cmd.RestartApp()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
308
cmd/launcher/main.go
Normal file
@ -0,0 +1,308 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/crash"
|
||||
"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/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||
"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/sirupsen/logrus"
|
||||
"golang.org/x/sys/execabs"
|
||||
)
|
||||
|
||||
const (
|
||||
appName = "Proton Mail Launcher"
|
||||
exeName = "bridge"
|
||||
guiName = "bridge-gui"
|
||||
|
||||
FlagCLI = "cli"
|
||||
FlagCLIShort = "c"
|
||||
FlagNonInteractive = "noninteractive"
|
||||
FlagNonInteractiveShort = "n"
|
||||
FlagLauncher = "--launcher"
|
||||
FlagWait = "--wait"
|
||||
)
|
||||
|
||||
func main() { //nolint:funlen
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
l := logrus.WithField("launcher_version", constants.Version)
|
||||
|
||||
reporter := sentry.NewReporter(appName, constants.Version, useragent.New())
|
||||
|
||||
crashHandler := crash.NewHandler(reporter.ReportException)
|
||||
defer crashHandler.HandlePanic()
|
||||
|
||||
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, constants.ConfigName))
|
||||
if err != nil {
|
||||
l.WithError(err).Fatal("Failed to get locations provider")
|
||||
}
|
||||
|
||||
locations := locations.New(locationsProvider, constants.ConfigName)
|
||||
|
||||
logsPath, err := locations.ProvideLogsPath()
|
||||
if err != nil {
|
||||
l.WithError(err).Fatal("Failed to get logs path")
|
||||
}
|
||||
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
|
||||
|
||||
if err := logging.Init(logsPath, os.Getenv("VERBOSITY")); err != nil {
|
||||
l.WithError(err).Fatal("Failed to setup logging")
|
||||
}
|
||||
|
||||
updatesPath, err := locations.ProvideUpdatesPath()
|
||||
if err != nil {
|
||||
l.WithError(err).Fatal("Failed to get updates path")
|
||||
}
|
||||
|
||||
key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey)
|
||||
if err != nil {
|
||||
l.WithError(err).Fatal("Failed to create new verification key")
|
||||
}
|
||||
|
||||
kr, err := crypto.NewKeyRing(key)
|
||||
if err != nil {
|
||||
l.WithError(err).Fatal("Failed to create new verification keyring")
|
||||
}
|
||||
|
||||
versioner := versioner.New(updatesPath)
|
||||
|
||||
launcher, err := os.Executable()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to determine path to launcher")
|
||||
}
|
||||
|
||||
l = l.WithField("launcher_path", launcher)
|
||||
|
||||
args := os.Args[1:]
|
||||
|
||||
exe, err := getPathToUpdatedExecutable(filepath.Base(launcher), versioner, kr, reporter)
|
||||
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, mainExe := findAndStripWait(args)
|
||||
if wait {
|
||||
waitForProcessToFinish(mainExe)
|
||||
}
|
||||
|
||||
cmd := execabs.Command(exe, appendLauncherPath(launcher, args)...) //nolint:gosec
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = os.Environ()
|
||||
|
||||
// On windows, if you use Run(), a terminal stays open; we don't want that.
|
||||
if //goland:noinspection GoBoolExpressions
|
||||
runtime.GOOS == "windows" {
|
||||
err = cmd.Start()
|
||||
} else {
|
||||
err = cmd.Run()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
l.WithError(err).Fatal("Failed to launch")
|
||||
}
|
||||
}
|
||||
|
||||
// appendLauncherPath add launcher path if missing.
|
||||
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...)
|
||||
|
||||
hasFlag := false
|
||||
var value string
|
||||
|
||||
for k, v := range res {
|
||||
if v != FlagWait {
|
||||
continue
|
||||
}
|
||||
if k+1 >= len(res) {
|
||||
continue
|
||||
}
|
||||
hasFlag = true
|
||||
value = res[k+1]
|
||||
}
|
||||
|
||||
if hasFlag {
|
||||
res, _ = findAndStrip(res, FlagWait)
|
||||
res, _ = findAndStrip(res, value)
|
||||
}
|
||||
return res, hasFlag, value
|
||||
}
|
||||
|
||||
func getPathToUpdatedExecutable(
|
||||
name string,
|
||||
ver *versioner.Versioner,
|
||||
kr *crypto.KeyRing,
|
||||
reporter *sentry.Reporter,
|
||||
) (string, error) {
|
||||
versions, err := ver.ListVersions()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to list available versions")
|
||||
}
|
||||
|
||||
currentVersion, err := semver.StrictNewVersion(constants.Version)
|
||||
if err != nil {
|
||||
logrus.WithField("version", constants.Version).WithError(err).Error("Failed to parse current version")
|
||||
}
|
||||
|
||||
for _, version := range versions {
|
||||
vlog := logrus.WithFields(logrus.Fields{
|
||||
"version": constants.Version,
|
||||
"check_version": version,
|
||||
"name": name,
|
||||
})
|
||||
|
||||
if err := version.VerifyFiles(kr); err != nil {
|
||||
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 {
|
||||
vlog.WithError(err).Error("Failed to remove files")
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip versions that are less or equal to launcher version.
|
||||
if currentVersion != nil && !version.SemVer().GreaterThan(currentVersion) {
|
||||
continue
|
||||
}
|
||||
|
||||
exe, err := version.GetExecutable(name)
|
||||
if err != nil {
|
||||
vlog.WithError(err).Error("Failed to get executable")
|
||||
continue
|
||||
}
|
||||
|
||||
return exe, nil
|
||||
}
|
||||
|
||||
return "", errors.New("no available newer versions")
|
||||
}
|
||||
|
||||
// waitForProcessToFinish waits until the process with the given path is finished.
|
||||
func waitForProcessToFinish(exePath string) {
|
||||
for {
|
||||
processes, err := sysinfo.Processes()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("Could not determine running processes")
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
logrus.WithError(err).WithField("file", path).Error("Could not retrieve file info")
|
||||
return false
|
||||
}
|
||||
|
||||
return os.SameFile(pathInfo, info)
|
||||
}
|
||||
58
cmd/launcher/main_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
// 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 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{}))
|
||||
}
|
||||
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 |
36
dist/info.rc
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
#define STRINGIZE_(x) #x
|
||||
#define STRINGIZE(x) STRINGIZE_(x)
|
||||
|
||||
IDI_ICON1 ICON DISCARDABLE STRINGIZE(ICO_FILE)
|
||||
|
||||
#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 INTERNAL_NAME STRINGIZE(EXE_NAME)
|
||||
#define PRODUCT_NAME "Proton Mail Bridge for Windows"
|
||||
|
||||
#define LEGAL_COPYRIGHT "(C) " STRINGIZE(YEAR) " Proton AG"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION_COMMA,0
|
||||
PRODUCTVERSION FILE_VERSION_COMMA,0
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "Comments", FILE_COMMENTS
|
||||
VALUE "CompanyName", "Proton AG"
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", STRINGIZE(FILE_VERSION)
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", LEGAL_COPYRIGHT
|
||||
VALUE "OriginalFilename", STRINGIZE(ORIGINAL_FILE_NAME)
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", STRINGIZE(PRODUCT_VERSION)
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x0409, 0x04B0
|
||||
END
|
||||
END
|
||||
11
dist/proton-bridge.desktop
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Version=1.1
|
||||
Name=Proton Mail Bridge
|
||||
GenericName=Proton Mail Bridge for Linux
|
||||
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
|
||||
Exec=protonmail-bridge
|
||||
Terminal=false
|
||||
Categories=Office;Email;Network
|
||||
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 |
@ -51,7 +51,7 @@ PMAPI directly.
|
||||
graph TD
|
||||
|
||||
C["Client (e.g. Thunderbird)"]
|
||||
PM[ProtonMail Server]
|
||||
PM[Proton Mail Server]
|
||||
|
||||
subgraph "Bridge app"
|
||||
subgraph "Bridge core"
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
# Encryption
|
||||
|
||||
Encryption is done in PMAPI, bridge utils and bridge itself. The best would be to keep encryption
|
||||
in PMAPI and bridge utils (in pacakge such as messages). All packages are using our high-level
|
||||
GopenPGP library on top of openpgp.
|
||||
in PMAPI and bridge utils (in package such as messages). All packages are using our high-level
|
||||
GopenPGP library on top of OpenPGP.
|
||||
|
||||
## `gopenpgp.KeyRing`
|
||||
|
||||
We use one `KeyRing` per address. Our usage then contains all keys for specific address. Primary
|
||||
key is always on the first position, then there old ones to be able to decrypt last e-mail.
|
||||
Openpgp encrypts given message with all available keys, so we need to first get first (primary)
|
||||
OpenPGP encrypts given message with all available keys, so we need to first get first (primary)
|
||||
key for encryption to have message encrypted only once with primary key.
|
||||
|
||||
@ -1,135 +0,0 @@
|
||||
# Import-Export app
|
||||
|
||||
## Main blocks
|
||||
|
||||
This is basic overview of the main Import-Export blocks.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
S[ProtonMail server]
|
||||
U[User]
|
||||
|
||||
subgraph "Import-Export app"
|
||||
Users
|
||||
Frontend["Qt / CLI"]
|
||||
ImportExport
|
||||
Transfer
|
||||
|
||||
Frontend --> ImportExport
|
||||
Frontend --> Transfer
|
||||
ImportExport --> Users
|
||||
ImportExport --> Transfer
|
||||
end
|
||||
|
||||
EML --> Transfer
|
||||
MBOX --> Transfer
|
||||
IMAP --> Transfer
|
||||
S --> Transfer
|
||||
|
||||
Transfer --> EML
|
||||
Transfer --> MBOX
|
||||
Transfer --> S
|
||||
|
||||
U --> Frontend
|
||||
```
|
||||
|
||||
## Code structure
|
||||
|
||||
More detailed graph of main types used in Import-Export app and connection between them.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
PM[ProtonMail Server]
|
||||
EML[EML]
|
||||
MBOX[MBOX]
|
||||
IMAP[IMAP]
|
||||
|
||||
subgraph "Import-Export app"
|
||||
subgraph "pkg users"
|
||||
subgraph "pkg credentials"
|
||||
CredStore[Store]
|
||||
Creds[Credentials]
|
||||
|
||||
CredStore --> Creds
|
||||
end
|
||||
|
||||
US[Users]
|
||||
U[User]
|
||||
|
||||
US --> U
|
||||
end
|
||||
|
||||
subgraph "pkg frontend"
|
||||
CLI
|
||||
Qt
|
||||
end
|
||||
|
||||
subgraph "pkg importExport"
|
||||
IE[ImportExport]
|
||||
end
|
||||
|
||||
subgraph "pkg transfer"
|
||||
Transfer
|
||||
Rules
|
||||
Progress
|
||||
|
||||
Provider
|
||||
LocalProvider
|
||||
EMLProvider
|
||||
MBOXProvider
|
||||
IMAPProvider
|
||||
PMAPIProvider
|
||||
|
||||
Mailbox
|
||||
Message
|
||||
|
||||
Transfer --> |source|Provider
|
||||
Transfer --> |target|Provider
|
||||
Transfer --> Rules
|
||||
Transfer --> Progress
|
||||
|
||||
Provider --> LocalProvider
|
||||
Provider --> EMLProvider
|
||||
Provider --> MBOXProvider
|
||||
Provider --> IMAPProvider
|
||||
Provider --> PMAPIProvider
|
||||
|
||||
LocalProvider --> EMLProvider
|
||||
LocalProvider --> MBOXProvider
|
||||
|
||||
Provider --> Mailbox
|
||||
Provider --> Message
|
||||
|
||||
end
|
||||
|
||||
subgraph PMAPI
|
||||
APIM[ClientManager]
|
||||
APIC[Client]
|
||||
|
||||
APIM --> APIC
|
||||
end
|
||||
end
|
||||
|
||||
CLI --> IE
|
||||
CLI --> Transfer
|
||||
CLI --> Progress
|
||||
Qt --> IE
|
||||
Qt --> Transfer
|
||||
Qt --> Progress
|
||||
|
||||
U --> CredStore
|
||||
U --> Creds
|
||||
|
||||
US --> APIM
|
||||
U --> APIM
|
||||
|
||||
PMAPIProvider --> APIM
|
||||
EMLProvider --> EML
|
||||
MBOXProvider --> MBOX
|
||||
IMAPProvider --> IMAP
|
||||
|
||||
IE --> US
|
||||
IE --> Transfer
|
||||
|
||||
APIC --> PM
|
||||
```
|
||||
@ -1,14 +1,9 @@
|
||||
# Documentation
|
||||
# Bridge Documentation
|
||||
|
||||
Documentation pages in order to read for a novice:
|
||||
|
||||
## Bridge
|
||||
|
||||
* [Bridge code](bridge.md)
|
||||
* [Internal Bridge database](database.md)
|
||||
* [Communication between Bridge, Client and Server](communication.md)
|
||||
* [Encryption](encryption.md)
|
||||
|
||||
## Import-Export app
|
||||
|
||||
* [Import-Export code](importexport.md)
|
||||
|
||||
103
doc/updates.md
Normal file
@ -0,0 +1,103 @@
|
||||
# Update mechanism of Bridge
|
||||
|
||||
There are mulitple options how to change version of application:
|
||||
* Automatic in-app update
|
||||
* Manual in-app update
|
||||
* Manual install
|
||||
|
||||
In-app update ends with restarting bridge into new version. Automatic in-app
|
||||
update is downloading, verifying and installing the new version immediatelly
|
||||
without user confirmation. For manual in-app update user needs to confirm first.
|
||||
Update is done from special update file published on website.
|
||||
|
||||
The manual installation requires user to download, verify and install manually
|
||||
using installer for given OS.
|
||||
|
||||
The bridge is installed and executed differently for given OS:
|
||||
|
||||
* Windows and Linux apps are using launcher mechanism:
|
||||
* There is system protected installation path which is created on first
|
||||
install. It contains bridge exe and launcher exe. When users starts
|
||||
bridge the launcher is executed first. It will check update path compare
|
||||
version with installed one. The newer version then is then executed.
|
||||
* Update mechanism means to replace files in update folder which is located
|
||||
in user space.
|
||||
|
||||
* macOS app does not use launcher
|
||||
* No launcher, only one executable
|
||||
* In-App udpate replaces the bridge files in installation path directly
|
||||
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Frontend
|
||||
U[User requests<br>version check]
|
||||
ManIns((Notify user about<br>manual install<br>is needed))
|
||||
R((Notify user<br>about restart))
|
||||
ManUp((Notify user about<br>manual update))
|
||||
NF((Notify user about<br>force update))
|
||||
|
||||
ManUp -->|Install| InstFront[Install]
|
||||
InstFront -->|Ok| R
|
||||
InstFront -->|Error| ManIns
|
||||
|
||||
U --> CheckFront[Check online]
|
||||
CheckFront -->|Ok| IAFront{Is new version<br>and applicable?}
|
||||
CheckFront -->|Error| ManIns
|
||||
|
||||
IAFront -->|No| Latest((Notify user<br>has latest version))
|
||||
IAFront -->|Yes| CanInstall{Can update?}
|
||||
CanInstall -->|No| ManIns
|
||||
CanInstall -->|Yes| NotifOrInstall{Is automatic<br>update enabled?}
|
||||
NotifOrInstall -->|Manual| ManUp
|
||||
end
|
||||
|
||||
|
||||
subgraph Backend
|
||||
W[Wait for next check]
|
||||
|
||||
W --> Check[Check online]
|
||||
|
||||
Check --> NV{Has new<br>version?}
|
||||
Check -->|Error| W
|
||||
NV -->|No new version| W
|
||||
IA{Is install<br>applicable?}
|
||||
NV -->|New version<br>available| IA
|
||||
IA -->|Local rollout<br>not enough| W
|
||||
IA -->|Yes| AU{Is automatic\nupdate enabled?}
|
||||
|
||||
AU -->|Yes| CanUp{Can update?}
|
||||
CanUp -->|No| ManIns
|
||||
|
||||
CanUp -->|Yes| Ins[Install]
|
||||
Ins -->|Error| ManIns
|
||||
Ins -->|Ok| R
|
||||
|
||||
AU -->|No| ManUp
|
||||
ManUp -->|Ignore| W
|
||||
|
||||
|
||||
F[Force update]
|
||||
F --> NF
|
||||
end
|
||||
|
||||
ManIns --> Web[Open web page]
|
||||
NF --> Web
|
||||
ManUp --> Web
|
||||
R --> Re[Restart]
|
||||
NF --> Q[Quit bridge]
|
||||
NotifOrInstall -->|Automatic| W
|
||||
```
|
||||
|
||||
|
||||
The non-trivial is to combine the update with setting change:
|
||||
* turn off/on automatic in-app updates
|
||||
* change from stable to beta or back
|
||||
|
||||
_TODO fill flow chart details_
|
||||
|
||||
|
||||
We are not support downgrade functionality. Only some circumstances can lead to
|
||||
downgrading the app version.
|
||||
|
||||
_TODO fill flow chart details_
|
||||
1
extern/vcpkg
vendored
Submodule
178
go.mod
@ -1,82 +1,126 @@
|
||||
module github.com/ProtonMail/proton-bridge
|
||||
module github.com/ProtonMail/proton-bridge/v3
|
||||
|
||||
go 1.13
|
||||
go 1.18
|
||||
|
||||
// These dependencies are `replace`d below, so the version numbers should be ignored.
|
||||
// They are in a separate require block to highlight this.
|
||||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||
github.com/Masterminds/semver/v3 v3.1.1
|
||||
github.com/ProtonMail/gluon v0.14.2-0.20221129150032-c663738a6cee
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
github.com/ProtonMail/go-proton-api v0.1.2
|
||||
github.com/ProtonMail/go-rfc5322 v0.11.0
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.4.10
|
||||
github.com/PuerkitoBio/goquery v1.8.0
|
||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37
|
||||
github.com/bradenaw/juniper v0.8.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/emersion/go-smtp v0.0.0-20180712174835-db5eec195e67
|
||||
github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
github.com/elastic/go-sysinfo v1.8.1
|
||||
github.com/emersion/go-imap v1.2.1-0.20220429085312-746087b7a317
|
||||
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/fatih/color v1.13.0
|
||||
github.com/getsentry/sentry-go v0.15.0
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/goccy/go-json v0.9.11
|
||||
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/keybase/go-keychain v0.0.0
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/profile v1.6.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/urfave/cli/v2 v2.20.3
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5
|
||||
go.uber.org/goleak v1.2.0
|
||||
golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e
|
||||
golang.org/x/net v0.1.0
|
||||
golang.org/x/sys v0.1.0
|
||||
golang.org/x/text v0.4.0
|
||||
google.golang.org/grpc v1.50.1
|
||||
google.golang.org/protobuf v1.28.1
|
||||
howett.net/plist v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1
|
||||
github.com/Masterminds/semver/v3 v3.1.0
|
||||
github.com/ProtonMail/go-appdir v1.1.0
|
||||
github.com/ProtonMail/go-apple-mobileconfig v0.0.0-20160701194735-7ea9927a11f6
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
|
||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
||||
github.com/ProtonMail/go-rfc5322 v0.2.0
|
||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.0.1
|
||||
github.com/PuerkitoBio/goquery v1.5.1
|
||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||
ariga.io/atlas v0.7.0 // indirect
|
||||
entgo.io/ent v0.11.2 // indirect
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect
|
||||
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f // indirect
|
||||
github.com/ProtonMail/go-srp v0.0.5 // indirect
|
||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
|
||||
github.com/chzyer/logex v1.1.10 // indirect
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
||||
github.com/cucumber/godog v0.8.1
|
||||
github.com/emersion/go-imap v1.0.6-0.20200708083111-011063d6c9df
|
||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a
|
||||
github.com/emersion/go-imap-idle v0.0.0-20200601154248-f05f54664cc4
|
||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
|
||||
github.com/emersion/go-imap-quota v0.0.0-20200423100218-dcfd1b7d2b41
|
||||
github.com/emersion/go-imap-specialuse v0.0.0-20200722111535-598ff00e4075
|
||||
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26
|
||||
github.com/emersion/go-mbox v1.0.2
|
||||
github.com/emersion/go-message v0.12.1-0.20200903165315-e1abe21f389a
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe
|
||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 // indirect
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/chzyer/test v1.0.0 // indirect
|
||||
github.com/cloudflare/circl v1.2.0 // indirect
|
||||
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/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
|
||||
github.com/emersion/go-vcard v0.0.0-20220507122617-d4056df0ec4a // indirect
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/getsentry/raven-go v0.2.0
|
||||
github.com/go-resty/resty/v2 v2.3.0
|
||||
github.com/golang/mock v1.4.4
|
||||
github.com/google/go-cmp v0.5.1
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/miekg/dns v1.1.30
|
||||
github.com/myesui/uuid v1.0.0 // indirect
|
||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce
|
||||
github.com/olekukonko/tablewriter v0.0.4 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.8.1 // indirect
|
||||
github.com/go-openapi/inflect v0.19.0 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.1 // indirect
|
||||
github.com/gofrs/uuid v4.3.0+incompatible // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-memdb v1.3.3 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.14.0 // indirect
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // 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.5 // 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/stretchr/testify v1.6.1
|
||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
|
||||
github.com/twinj/uuid v1.0.0 // indirect
|
||||
github.com/urfave/cli v1.22.4
|
||||
go.etcd.io/bbolt v1.3.5
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||
golang.org/x/text v0.3.3
|
||||
gopkg.in/stretchr/testify.v1 v1.2.2 // indirect
|
||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/zclconf/go-cty v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect
|
||||
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa // indirect
|
||||
google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
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-20201102134601-418cd74e9474
|
||||
github.com/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20181206232543-8261df20d309
|
||||
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998
|
||||
golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20200818122824-ed5d25e28db8
|
||||
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.0.0-20210611055058-fabeff2ec753
|
||||
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe
|
||||
)
|
||||
|
||||
713
go.sum
@ -1,218 +1,615 @@
|
||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 h1:j9HaafapDbPbGRDku6e/HRs6KBMcKHiWcm1/9Sbxnl4=
|
||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
|
||||
ariga.io/atlas v0.7.0 h1:daEFdUsyNm7EHyzcMfjWwq/fVv48fCfad+dIGyobY1k=
|
||||
ariga.io/atlas v0.7.0/go.mod h1:ft47uSh5hWGDCmQC9DsztZg6Xk+KagM5Ts/mZYKb9JE=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
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=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
entgo.io/ent v0.11.2 h1:UM2/BUhF2FfsxPHRxLjQbhqJNaDdVlOwNIAMLs2jyto=
|
||||
entgo.io/ent v0.11.2/go.mod h1:YGHEQnmmIUgtD5b1ICD5vg74dS3npkNnmC5K+0J+IHU=
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 h1:l6surSnJ3RP4qA1qmKJ+hQn3UjytosdoG27WGjrDlVs=
|
||||
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/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
|
||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998 h1:YT2uVwQiRQZxCaaahwfcgTq2j3j66w00n/27gb/zubs=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||
github.com/ProtonMail/crypto v0.0.0-20200818122824-ed5d25e28db8 h1:u1j0xLTrCHpNS40B6m4Sv3IVUz5m9jt+AnTIopT3IgM=
|
||||
github.com/ProtonMail/crypto v0.0.0-20200818122824-ed5d25e28db8/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
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/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
||||
github.com/ProtonMail/go-appdir v1.1.0 h1:9hdNDlU9kTqRKVNzmoqah8qqrj5QZyLByQdwQNlFWig=
|
||||
github.com/ProtonMail/go-appdir v1.1.0/go.mod h1:3d8Y9F5mbEUjrYbcJ3rcDxcWbqbttF+011nVZmdRdzc=
|
||||
github.com/ProtonMail/go-apple-mobileconfig v0.0.0-20160701194735-7ea9927a11f6 h1:YsSJ/mvZFYydQm/hRrt8R8UtgETixN2y3LK98f5LT60=
|
||||
github.com/ProtonMail/go-apple-mobileconfig v0.0.0-20160701194735-7ea9927a11f6/go.mod h1:EtDfBMIDWmVe4viZCuBTEfe3OIIo0ghbpOaAZVO+hVg=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||
github.com/ProtonMail/go-imap v0.0.0-20201102134601-418cd74e9474 h1:D0RwDtkBw0Gt7hmbb1ivdEulplJAwu1i2jzh4HM45fo=
|
||||
github.com/ProtonMail/go-imap v0.0.0-20201102134601-418cd74e9474/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
|
||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:5koQozTDELymYOyFbQ/VSubexAEXzDR8qGM5mO8GRdw=
|
||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
||||
github.com/ProtonMail/go-rfc5322 v0.2.0 h1:tndoDGFtiCvESta9KLUeMksojz8qf76PefnkoQ+fqeg=
|
||||
github.com/ProtonMail/go-rfc5322 v0.2.0/go.mod h1:mzZWlMWnQJuYLL7JpzuPF5+FimV2lZ9f0jeq24kJjpU=
|
||||
github.com/ProtonMail/go-smtp v0.0.0-20181206232543-8261df20d309 h1:2pzfKjhBjSnw3BgmfTYRFQr1rFGxhfhUY0KKkg+RYxE=
|
||||
github.com/ProtonMail/go-smtp v0.0.0-20181206232543-8261df20d309/go.mod h1:6UoBvDAMA/cTBwS3Y7tGpKnY5RH1F1uYHschT6eqAkI=
|
||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
|
||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.0.1 h1:x0uvDhry5WzoHeJO4J3dgMLhG4Z9PeBJ2O+sDOY0LcU=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.0.1/go.mod h1:wQQCJo7DURO6S9VwH+kSDEYs/B63yZnAEfGlOg8YNBY=
|
||||
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/ProtonMail/gluon v0.14.2-0.20221129150032-c663738a6cee h1:rDGqVa4CepqpJF8TDjqnBITqD8OzrLzeg66ibVDCPSc=
|
||||
github.com/ProtonMail/gluon v0.14.2-0.20221129150032-c663738a6cee/go.mod h1:z2AxLIiBCT1K+0OBHyaDI7AEaO5qI6/BEC2TE42vs4Q=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20220822140716-1678d6eb0cbe/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
|
||||
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac h1:2xU3QncAiS/W3UlWZTkbNKW5WkLzk6Egl1T0xX+sbjs=
|
||||
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
|
||||
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753 h1:I8IsYA297x0QLU80G5I6aLYUu3JYNSpo8j5fkXtFDW0=
|
||||
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f h1:4IWzKjHzZxdrW9k4zl/qCwenOVHDbVDADPPHFLjs0Oc=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f/go.mod h1:qRZgbeASl2a9OwmsV85aWwRqic0NHPh+9ewGAzb4cgM=
|
||||
github.com/ProtonMail/go-proton-api v0.1.2 h1:MD0lbo8ohU1O+1mbMU6EkDmVj4BAq5e5cCPkIZgDF9Q=
|
||||
github.com/ProtonMail/go-proton-api v0.1.2/go.mod h1:jqvJ2HqLHqiPJoEb+BTIB1IF7wvr6p+8ZfA6PO2NRNk=
|
||||
github.com/ProtonMail/go-rfc5322 v0.11.0 h1:o5Obrm4DpmQEffvgsVqG6S4BKwC1Wat+hYwjIp2YcCY=
|
||||
github.com/ProtonMail/go-rfc5322 v0.11.0/go.mod h1:6oOKr0jXvpoE6pwTx/HukigQpX2J9WUf6h0auplrFTw=
|
||||
github.com/ProtonMail/go-srp v0.0.5 h1:xhUioxZgDbCnpo9JehyFhwwsn9JLWkUGfB0oiKXgiGg=
|
||||
github.com/ProtonMail/go-srp v0.0.5/go.mod h1:06iYHtLXW8vjLtccWj++x3MKy65sIT8yZd7nrJF49rs=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.4.10 h1:EYgkxzwmQvsa6kxxkgP1AwzkFqKHscF2UINxaSn6rdI=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.4.10/go.mod h1:CTRA7/toc/4DxDy5Du4hPDnIZnJvXSeQ8LsRTOUJoyc=
|
||||
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||
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/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
|
||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
|
||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc h1:mZca0/HZ/XWXP9txkfdl2GH6mUzBqAlyJz3u5Lg8fuA=
|
||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc/go.mod h1:qqsTQiwdyqxU05iDCsi0oN3P4nrVxAmn8xCtODDSf/U=
|
||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c h1:j/C2kxPfyE0d87/ggAjIsCV5Cdkqmjb+O0W8W+1J+IY=
|
||||
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA=
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
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/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
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/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37 h1:28uU3TtuvQ6KRndxg9TrC868jBWmSKgh0GTXkACCXmA=
|
||||
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37/go.mod h1:6AXRstqK+32jeFmw89QGL2748+dj34Av4xc/I9oo9BY=
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220816024939-bc8df83d7b9d/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
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/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/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/bradenaw/juniper v0.8.0 h1:sdanLNdJbLjcLj993VYIwUHlUVkLzvgiD/x9O7cvvxk=
|
||||
github.com/bradenaw/juniper v0.8.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.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
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/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||
github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec=
|
||||
github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
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/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
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.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cucumber/godog v0.8.1 h1:lVb+X41I4YDreE+ibZ50bdXmySxgRviYFgKY6Aw4XE8=
|
||||
github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA=
|
||||
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
|
||||
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/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw=
|
||||
github.com/cucumber/godog v0.12.5 h1:FZIy6VCfMbmGHts9qd6UjBMT9abctws/pQYO/ZcwOVs=
|
||||
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.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
|
||||
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-20220405075754-31e7cee908fe/go.mod h1:ZoZU1fnBy3mOLWr3Pg+Y2+nTKtu6ypDte2kZg9HvSwY=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a h1:bMdSPm6sssuOFpIaveu3XGAijMS3Tq2S3EqFZmZxidc=
|
||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a/go.mod h1:ikgISoP7pRAolqsVP64yMteJa2FIpS6ju88eBT6K1yQ=
|
||||
github.com/emersion/go-imap-idle v0.0.0-20200601154248-f05f54664cc4 h1:/JIALzmCduf5o8TWJSiOBzTb9+R0SChwElUrJLlp2po=
|
||||
github.com/emersion/go-imap-idle v0.0.0-20200601154248-f05f54664cc4/go.mod h1:o14zPKCmEH5WC1vU5SdPoZGgNvQx7zzKSnxPQlobo78=
|
||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342 h1:5p1t3e1PomYgLWwEwhwEU5kVBwcyAcVrOpexv8AeZx0=
|
||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
|
||||
github.com/emersion/go-imap-quota v0.0.0-20200423100218-dcfd1b7d2b41 h1:z5lDGnSURauBEDdNLj3o0+HogVYKQCGeY3Anl/xyRfU=
|
||||
github.com/emersion/go-imap-quota v0.0.0-20200423100218-dcfd1b7d2b41/go.mod h1:iApyhIQBiU4XFyr+3kdJyyGqle82TbQyuP2o+OZHrV0=
|
||||
github.com/emersion/go-imap-specialuse v0.0.0-20200722111535-598ff00e4075 h1:z8TiDE4yqtzNeA1yb6ZRcktd+BHlXQbKGugvmDuc488=
|
||||
github.com/emersion/go-imap-specialuse v0.0.0-20200722111535-598ff00e4075/go.mod h1:/nybxhI8kXom8Tw6BrHMl42usALvka6meORflnnYwe4=
|
||||
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-mbox v1.0.2 h1:tE/rT+lEugK9y0myEymCCHnwlZN04hlXPrbKkxRBA5I=
|
||||
github.com/emersion/go-mbox v1.0.2/go.mod h1:Yp9IVuuOYLEuMv4yjgDHvhb5mHOcYH6x92Oas3QqEZI=
|
||||
github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
|
||||
github.com/emersion/go-message v0.12.1-0.20200903165315-e1abe21f389a h1:3C6qIGgPr1qAT0ikRD5NbyKpME/iHCDeXhpv/JJsFsE=
|
||||
github.com/emersion/go-message v0.12.1-0.20200903165315-e1abe21f389a/go.mod h1:kYIioST9GDHte9/BRWgi93rpqbDuFftMjKSMaXS8ABo=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/elastic/go-sysinfo v1.8.1 h1:4Yhj+HdV6WjbCRgGdZpPJ8lZQlXZLKDAeIkmQ/VRvi4=
|
||||
github.com/elastic/go-sysinfo v1.8.1/go.mod h1:JfllUnzoQV/JRYymbH3dO1yggI3mV2oTKSXsDHM+uIM=
|
||||
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/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:43mBoVwooyLm1+1YVf5nvn1pSFWhw7rOpcrp1Jg/qk0=
|
||||
github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:sPwp0FFboaK/bxsrUz1lNrDMUCsZUsKC5YuM4uRVRVs=
|
||||
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-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/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-20190105225839-8856043f13c5/go.mod h1:WIi9g8OKJQHXtQbx7GExlo6UAFaui9WDMYabJ+Be4WI=
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d h1:hFRM6zCBSc+Xa0rBOqSlG6Qe9dKC/2vLhGAuZlWxTsc=
|
||||
github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
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-vcard v0.0.0-20220507122617-d4056df0ec4a h1:cltZpe6s0SJtqK5c/5y2VrIYi8BAtDM6qjmiGYqfTik=
|
||||
github.com/emersion/go-vcard v0.0.0-20220507122617-d4056df0ec4a/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
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/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So=
|
||||
github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
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/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
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.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
|
||||
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
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.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
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/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.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
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.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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
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/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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/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-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
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.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/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-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/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/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
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/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
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.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE=
|
||||
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-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
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.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-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
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-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-uuid v1.0.0/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/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
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.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/hcl/v2 v2.14.0 h1:jX6+Q38Ly9zaAJlAjnFVyeNSNCKKW8D0wvyg7vij5Wc=
|
||||
github.com/hashicorp/hcl/v2 v2.14.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
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/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba h1:QFQpJdgbON7I0jr2hYW7Bs+XV0qjc3d5tZoDnRFnqTg=
|
||||
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
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/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
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/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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
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/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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/martinlindhe/base36 v1.0.0 h1:eYsumTah144C0A8P1T/AVSUk5ZoLnhfYFM3OGQxB52A=
|
||||
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
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.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo=
|
||||
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/myesui/uuid v1.0.0 h1:xCBmH4l5KuvLYc5L7AS7SZg9/jKdIFubM7OVoLqaQUI=
|
||||
github.com/myesui/uuid v1.0.0/go.mod h1:2CDfNgU0LR8mIdO8vdWd8i9gWWxLlcoIGGpSNgafq84=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
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/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
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.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
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/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-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
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/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/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
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/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM=
|
||||
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
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/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
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/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
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/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
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/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
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/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
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/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/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
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/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.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
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.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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk=
|
||||
github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY=
|
||||
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/urfave/cli/v2 v2.20.3 h1:lOgGidH/N5loaigd9HjFsOIhXSTrzl7tBpHswZ428w4=
|
||||
github.com/urfave/cli/v2 v2.20.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0=
|
||||
github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
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/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
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-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-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e h1:SkwG94eNiiYJhbeDE018Grw09HIN/KB9NlRmZsrzfWs=
|
||||
golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
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/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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
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-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.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
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-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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190311183353-d8887717615a/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-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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
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-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A=
|
||||
golang.org/x/sync v0.0.0-20220907140024-f12130a52804/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-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-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-20181116152217-5ac8a444bdc5/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-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-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-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-20190813064441-fde4db37ae7a/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/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-20220315194320-039c03cc5b86/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
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/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-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
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-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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
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-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-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-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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
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.13-0.20220804200503-81c7dc4e4efa h1:uKcci2q7Qtp6nMTC/AAvfNUAldFtJuHWV9/5QWiypts=
|
||||
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
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-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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/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-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 h1:K1zaaMdYBXRyX+cwFnxj7M6zwDyumLQMZ5xqwGvjreQ=
|
||||
google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737/go.mod h1:2r/26NEF3bFmT3eC3aZreahSal0C3Shl8Gi6vyDYqOQ=
|
||||
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.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
|
||||
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||
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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
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 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M=
|
||||
gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
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.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.4/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-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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.0-20210107192922-496545a6307b/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-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.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
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=
|
||||
|
||||
|
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,95 +0,0 @@
|
||||
// Copyright (c) 2020 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/>.
|
||||
|
||||
// Package api provides HTTP API of the Bridge.
|
||||
//
|
||||
// API endpoints:
|
||||
// * /focus, see focusHandler
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
||||
"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
|
||||
pref *config.Preferences
|
||||
tls *tls.Config
|
||||
certPath string
|
||||
keyPath string
|
||||
eventListener listener.Listener
|
||||
}
|
||||
|
||||
// NewAPIServer returns prepared API server struct.
|
||||
func NewAPIServer(pref *config.Preferences, tls *tls.Config, certPath, keyPath string, eventListener listener.Listener) *apiServer { //nolint[golint]
|
||||
return &apiServer{
|
||||
host: bridge.Host,
|
||||
pref: pref,
|
||||
tls: tls,
|
||||
certPath: certPath,
|
||||
keyPath: keyPath,
|
||||
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,
|
||||
TLSConfig: api.tls,
|
||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
||||
}
|
||||
|
||||
log.Info("API listening at ", addr)
|
||||
if err := server.ListenAndServeTLS(api.certPath, api.keyPath); 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.pref.GetInt(preferences.APIPortKey)
|
||||
newPort := ports.FindFreePortFrom(port)
|
||||
if newPort != port {
|
||||
api.pref.SetInt(preferences.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) 2020 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/>.
|
||||
|
||||
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,55 +0,0 @@
|
||||
// Copyright (c) 2020 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/>.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"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, tls *tls.Config) error {
|
||||
transport := &http.Transport{TLSClientConfig: tls}
|
||||
client := &http.Client{Transport: transport}
|
||||
|
||||
addr := getAPIAddress(bridge.Host, port)
|
||||
resp, err := client.Get("https://" + 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
|
||||
}
|
||||
409
internal/app/app.go
Normal file
@ -0,0 +1,409 @@
|
||||
// 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 app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"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/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/restarter"
|
||||
"github.com/pkg/profile"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Visible flags.
|
||||
const (
|
||||
flagCPUProfile = "cpu-prof"
|
||||
flagCPUProfileShort = "p"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
const (
|
||||
appUsage = "Proton Mail IMAP and SMTP Bridge"
|
||||
)
|
||||
|
||||
func New() *cli.App { //nolint:funlen
|
||||
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: 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,
|
||||
},
|
||||
}
|
||||
|
||||
app.Action = run
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func run(c *cli.Context) error { //nolint:funlen
|
||||
// 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, constants.Version, 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]
|
||||
}
|
||||
|
||||
migrationErr := migrateOldVersions()
|
||||
|
||||
// Run with profiling if requested.
|
||||
return withProfiler(c, func() error {
|
||||
// Restart the app if requested.
|
||||
return withRestarter(exe, func(restarter *restarter.Restarter) error {
|
||||
// Handle crashes with various actions.
|
||||
return withCrashHandler(restarter, reporter, func(crashHandler *crash.Handler, quitCh <-chan struct{}) 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() error {
|
||||
// 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.
|
||||
return withSingleInstance(locations, version, func() error {
|
||||
// Unlock the encrypted vault.
|
||||
return WithVault(locations, func(vault *vault.Vault, insecure, corrupt bool) error {
|
||||
if !vault.Migrated() {
|
||||
// Migrate old settings into the vault.
|
||||
if err := migrateOldSettings(vault); err != nil {
|
||||
logrus.WithError(err).Error("Failed to migrate old settings")
|
||||
}
|
||||
|
||||
// Migrate old accounts into the vault.
|
||||
if err := migrateOldAccounts(locations, vault); err != nil {
|
||||
logrus.WithError(err).Error("Failed to migrate old accounts")
|
||||
}
|
||||
|
||||
// The vault has been migrated.
|
||||
if err := vault.SetMigrated(); err != nil {
|
||||
logrus.WithError(err).Error("Failed to mark vault as migrated")
|
||||
}
|
||||
}
|
||||
|
||||
// Load the cookies from the vault.
|
||||
return withCookieJar(vault, func(cookieJar http.CookieJar) error {
|
||||
// Create a new bridge instance.
|
||||
return withBridge(c, exe, locations, version, identifier, crashHandler, reporter, vault, cookieJar, 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)
|
||||
}
|
||||
|
||||
// Run the frontend.
|
||||
return runFrontend(c, crashHandler, restarter, locations, b, eventCh, quitCh, c.Int(flagParentPID))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// If there's another instance already running, try to raise it and exit.
|
||||
func withSingleInstance(locations *locations.Locations, version *semver.Version, fn func() error) error {
|
||||
logrus.Debug("Checking for other instances")
|
||||
defer logrus.Debug("Single instance stopped")
|
||||
|
||||
lock, err := checkSingleInstance(locations.GetLockFile(), version)
|
||||
if err != nil {
|
||||
logrus.Info("Another instance is already running; raising it")
|
||||
|
||||
if ok := focus.TryRaise(); !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() 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.
|
||||
if err := logging.Init(logsPath, 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))
|
||||
|
||||
logrus.
|
||||
WithField("appName", constants.FullAppName).
|
||||
WithField("version", constants.Version).
|
||||
WithField("revision", constants.Revision).
|
||||
WithField("build", constants.BuildTime).
|
||||
WithField("runtime", runtime.GOOS).
|
||||
WithField("args", os.Args).
|
||||
Info("Run app")
|
||||
|
||||
return fn()
|
||||
}
|
||||
|
||||
// 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.
|
||||
locations := locations.New(provider, constants.ConfigName)
|
||||
defer func() {
|
||||
if err := locations.Clean(); err != nil {
|
||||
logrus.WithError(err).Error("Failed to clean locations")
|
||||
}
|
||||
}()
|
||||
|
||||
return fn(locations)
|
||||
}
|
||||
|
||||
// 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(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 crashHandler.HandlePanic()
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
163
internal/app/bridge.go
Normal file
@ -0,0 +1,163 @@
|
||||
// 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 app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"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/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const vaultSecretName = "bridge-vault-key"
|
||||
|
||||
// deleteOldGoIMAPFiles Set with `-ldflags -X app.deleteOldGoIMAPFiles=true` to enable cleanup of old imap cache data.
|
||||
var deleteOldGoIMAPFiles bool //nolint:gochecknoglobals
|
||||
|
||||
// withBridge creates creates and tears down the bridge.
|
||||
func withBridge( //nolint:funlen
|
||||
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,
|
||||
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)
|
||||
|
||||
// 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,
|
||||
|
||||
// The API stuff.
|
||||
constants.APIHost,
|
||||
cookieJar,
|
||||
identifier,
|
||||
pinningDialer,
|
||||
dialer.CreateTransportWithDialer(proxyDialer),
|
||||
proxyDialer,
|
||||
|
||||
// Crash and report stuff
|
||||
crashHandler,
|
||||
reporter,
|
||||
|
||||
// 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(
|
||||
updater.NewInstaller(versioner.New(updatesDir)),
|
||||
verifier,
|
||||
constants.UpdateName,
|
||||
runtime.GOOS,
|
||||
), nil
|
||||
}
|
||||
69
internal/app/frontend.go
Normal file
@ -0,0 +1,69 @@
|
||||
// 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 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).Loop()
|
||||
|
||||
case c.Bool(flagNonInteractive):
|
||||
select {}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
315
internal/app/migration.go
Normal file
@ -0,0 +1,315 @@
|
||||
// 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 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/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"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")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
settings, err := locations.ProvideSettingsPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get settings path: %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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return migratePrefsToVault(v, b)
|
||||
}
|
||||
|
||||
func migrateOldAccounts(locations *locations.Locations, 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")
|
||||
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)
|
||||
}
|
||||
|
||||
for _, userID := range users {
|
||||
logrus.WithField("userID", userID).Info("Migrating account")
|
||||
|
||||
creds, err := store.Get(userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get user: %w", err)
|
||||
}
|
||||
|
||||
authUID, authRef, err := creds.SplitAPIToken()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to split api token: %w", err)
|
||||
}
|
||||
|
||||
user, err := v.AddUser(creds.UserID, creds.EmailList()[0], authUID, authRef, creds.MailboxPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add user: %w", err)
|
||||
}
|
||||
|
||||
if err := user.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
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"`
|
||||
FirstStartGUI bool `json:"first_time_start_gui,,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.SetFirstStartGUI(prefs.FirstStartGUI); err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf("failed to migrate first start GUI: %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.SetSyncWorkers(prefs.FetchWorkers); err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf("failed to migrate sync workers: %w", err))
|
||||
}
|
||||
|
||||
if err := vault.SetSyncAttPool(prefs.AttachmentWorkers); err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf("failed to migrate sync attachment pool: %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()
|
||||
}
|
||||
81
internal/app/migration_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
// 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 app
|
||||
|
||||
import (
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/cookies"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMigratePrefsToVault(t *testing.T) {
|
||||
// Create a new vault.
|
||||
vault, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key"))
|
||||
require.NoError(t, err)
|
||||
require.False(t, corrupt)
|
||||
|
||||
// load the old prefs file.
|
||||
b, err := os.ReadFile(filepath.Join("testdata", "prefs.json"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Migrate the old prefs file to the new vault.
|
||||
require.NoError(t, migratePrefsToVault(vault, b))
|
||||
|
||||
// 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.True(t, vault.GetFirstStartGUI())
|
||||
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.Equal(t, 16, vault.SyncWorkers())
|
||||
require.Equal(t, 16, vault.SyncAttPool())
|
||||
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) 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 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(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.Debug("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()
|
||||
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)
|
||||
}
|
||||
31
internal/app/testdata/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"
|
||||
}
|
||||
143
internal/app/vault.go
Normal file
@ -0,0 +1,143 @@
|
||||
// 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 app
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"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"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func WithVault(locations *locations.Locations, 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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create vault: %w", err)
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"insecure": insecure,
|
||||
"corrupt": corrupt,
|
||||
}).Debug("Vault created")
|
||||
|
||||
// Install the certificates if needed.
|
||||
if installed := encVault.GetCertsInstalled(); !installed {
|
||||
logrus.Debug("Installing certificates")
|
||||
|
||||
if err := certs.NewInstaller().InstallCert(encVault.GetBridgeTLSCert()); err != nil {
|
||||
return fmt.Errorf("failed to install certs: %w", err)
|
||||
}
|
||||
|
||||
if err := encVault.SetCertsInstalled(true); err != nil {
|
||||
return fmt.Errorf("failed to set certs installed: %w", err)
|
||||
}
|
||||
|
||||
logrus.Debug("Certificates successfully installed")
|
||||
}
|
||||
|
||||
// GODT-1950: Add teardown actions (e.g. to close the vault).
|
||||
|
||||
return fn(encVault, insecure, corrupt)
|
||||
}
|
||||
|
||||
func newVault(locations *locations.Locations) (*vault.Vault, bool, bool, error) {
|
||||
vaultDir, err := locations.ProvideSettingsPath()
|
||||
if err != nil {
|
||||
return nil, false, false, 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 := getVaultKey(vaultDir); err != nil {
|
||||
insecure = true
|
||||
|
||||
// We store the insecure vault in a separate directory
|
||||
vaultDir = path.Join(vaultDir, "insecure")
|
||||
} else {
|
||||
vaultKey = key
|
||||
}
|
||||
|
||||
gluonDir, err := locations.ProvideGluonPath()
|
||||
if err != nil {
|
||||
return nil, false, false, fmt.Errorf("could not provide gluon path: %w", err)
|
||||
}
|
||||
|
||||
vault, corrupt, err := vault.New(vaultDir, gluonDir, vaultKey)
|
||||
if err != nil {
|
||||
return nil, false, false, fmt.Errorf("could not create vault: %w", err)
|
||||
}
|
||||
|
||||
return vault, insecure, corrupt, nil
|
||||
}
|
||||
|
||||
func getVaultKey(vaultDir string) ([]byte, error) {
|
||||
helper, err := vault.GetHelper(vaultDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get keychain helper: %w", err)
|
||||
}
|
||||
|
||||
keychain, err := keychain.NewKeychain(helper, constants.KeyChainName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create keychain: %w", err)
|
||||
}
|
||||
|
||||
secrets, err := keychain.List()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not list keychain: %w", err)
|
||||
}
|
||||
|
||||
if !slices.Contains(secrets, vaultSecretName) {
|
||||
tok, err := crypto.RandomToken(32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not generate random token: %w", err)
|
||||
}
|
||||
|
||||
if err := keychain.Put(vaultSecretName, base64.StdEncoding.EncodeToString(tok)); err != nil {
|
||||
return nil, fmt.Errorf("could not put keychain item: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, keyEnc, err := keychain.Get(vaultSecretName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get keychain item: %w", err)
|
||||
}
|
||||
|
||||
keyDec, err := base64.StdEncoding.DecodeString(keyEnc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not decode keychain item: %w", err)
|
||||
}
|
||||
|
||||
return keyDec, nil
|
||||
}
|
||||
82
internal/async/context.go
Normal file
@ -0,0 +1,82 @@
|
||||
// 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 async
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Abortable collects groups of functions that can be aborted by calling Abort.
|
||||
type Abortable struct {
|
||||
abortFunc []context.CancelFunc
|
||||
abortLock sync.RWMutex
|
||||
}
|
||||
|
||||
func (a *Abortable) Do(ctx context.Context, fn func(context.Context)) {
|
||||
fn(a.newCancelCtx(ctx))
|
||||
}
|
||||
|
||||
func (a *Abortable) Abort() {
|
||||
a.abortLock.RLock()
|
||||
defer a.abortLock.RUnlock()
|
||||
|
||||
for _, fn := range a.abortFunc {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Abortable) newCancelCtx(ctx context.Context) context.Context {
|
||||
a.abortLock.Lock()
|
||||
defer a.abortLock.Unlock()
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
a.abortFunc = append(a.abortFunc, cancel)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
// RangeContext iterates over the given channel until the context is canceled or the
|
||||
// channel is closed.
|
||||
func RangeContext[T any](ctx context.Context, ch <-chan T, fn func(T)) {
|
||||
for {
|
||||
select {
|
||||
case v, ok := <-ch:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
fn(v)
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ForwardContext forwards all values from the src channel to the dst channel until the
|
||||
// context is canceled or the src channel is closed.
|
||||
func ForwardContext[T any](ctx context.Context, dst chan<- T, src <-chan T) {
|
||||
RangeContext(ctx, src, func(v T) {
|
||||
select {
|
||||
case dst <- v:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
})
|
||||
}
|
||||
233
internal/async/group.go
Normal file
@ -0,0 +1,233 @@
|
||||
// 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 async
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PanicHandler interface {
|
||||
HandlePanic()
|
||||
}
|
||||
|
||||
// Group is forked and improved version of "github.com/bradenaw/juniper/xsync.Group".
|
||||
//
|
||||
// It manages a group of goroutines. The main change to original is posibility
|
||||
// to wait passed function to finish without canceling it's context and adding
|
||||
// PanicHandler.
|
||||
type Group struct {
|
||||
baseCtx context.Context
|
||||
ctx context.Context
|
||||
jobCtx context.Context
|
||||
cancel context.CancelFunc
|
||||
finish context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
|
||||
panicHandler PanicHandler
|
||||
}
|
||||
|
||||
// NewGroup returns a Group ready for use. The context passed to any of the f functions will be a
|
||||
// descendant of ctx.
|
||||
func NewGroup(ctx context.Context, panicHandler PanicHandler) *Group {
|
||||
bgCtx, cancel := context.WithCancel(ctx)
|
||||
jobCtx, finish := context.WithCancel(ctx)
|
||||
return &Group{
|
||||
baseCtx: ctx,
|
||||
ctx: bgCtx,
|
||||
jobCtx: jobCtx,
|
||||
cancel: cancel,
|
||||
finish: finish,
|
||||
panicHandler: panicHandler,
|
||||
}
|
||||
}
|
||||
|
||||
// Once calls f once from another goroutine.
|
||||
func (g *Group) Once(f func(ctx context.Context)) {
|
||||
g.wg.Add(1)
|
||||
go func() {
|
||||
defer g.handlePanic()
|
||||
|
||||
f(g.ctx)
|
||||
g.wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// jitterDuration returns a random duration in [d - jitter, d + jitter].
|
||||
func jitterDuration(d time.Duration, jitter time.Duration) time.Duration {
|
||||
return d + time.Duration(float64(jitter)*((rand.Float64()*2)-1)) //nolint:gosec
|
||||
}
|
||||
|
||||
// Periodic spawns a goroutine that calls f once per interval +/- jitter.
|
||||
func (g *Group) Periodic(
|
||||
interval time.Duration,
|
||||
jitter time.Duration,
|
||||
f func(ctx context.Context),
|
||||
) {
|
||||
g.wg.Add(1)
|
||||
go func() {
|
||||
defer g.handlePanic()
|
||||
|
||||
defer g.wg.Done()
|
||||
|
||||
t := time.NewTimer(jitterDuration(interval, jitter))
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
if g.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-g.jobCtx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
|
||||
t.Reset(jitterDuration(interval, jitter))
|
||||
f(g.ctx)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Trigger spawns a goroutine which calls f whenever the returned function is called. If f is
|
||||
// already running when triggered, f will run again immediately when it finishes.
|
||||
func (g *Group) Trigger(f func(ctx context.Context)) func() {
|
||||
c := make(chan struct{}, 1)
|
||||
g.wg.Add(1)
|
||||
go func() {
|
||||
defer g.handlePanic()
|
||||
|
||||
defer g.wg.Done()
|
||||
|
||||
for {
|
||||
if g.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-g.jobCtx.Done():
|
||||
return
|
||||
case <-c:
|
||||
}
|
||||
f(g.ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
select {
|
||||
case c <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PeriodicOrTrigger spawns a goroutine which calls f whenever the returned function is called. If
|
||||
// f is already running when triggered, f will run again immediately when it finishes. Also calls f
|
||||
// when it has been interval+/-jitter since the last trigger.
|
||||
func (g *Group) PeriodicOrTrigger(
|
||||
interval time.Duration,
|
||||
jitter time.Duration,
|
||||
f func(ctx context.Context),
|
||||
) func() {
|
||||
c := make(chan struct{}, 1)
|
||||
g.wg.Add(1)
|
||||
go func() {
|
||||
defer g.handlePanic()
|
||||
|
||||
defer g.wg.Done()
|
||||
|
||||
t := time.NewTimer(jitterDuration(interval, jitter))
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
if g.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-g.jobCtx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
t.Reset(jitterDuration(interval, jitter))
|
||||
case <-c:
|
||||
if !t.Stop() {
|
||||
<-t.C
|
||||
}
|
||||
t.Reset(jitterDuration(interval, jitter))
|
||||
}
|
||||
f(g.ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
select {
|
||||
case c <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Group) resetCtx() {
|
||||
g.jobCtx, g.finish = context.WithCancel(g.baseCtx)
|
||||
g.ctx, g.cancel = context.WithCancel(g.baseCtx)
|
||||
}
|
||||
|
||||
// Cancel is send to all of the spawn goroutines and ends periodic
|
||||
// or trigger routines.
|
||||
func (g *Group) Cancel() {
|
||||
g.cancel()
|
||||
g.finish()
|
||||
g.resetCtx()
|
||||
}
|
||||
|
||||
// Finish will ends all periodic or polls routines. It will let
|
||||
// currently running functions to finish (cancel is not sent).
|
||||
//
|
||||
// It is not safe to call Wait concurrently with any other method on g.
|
||||
func (g *Group) Finish() {
|
||||
g.finish()
|
||||
g.jobCtx, g.finish = context.WithCancel(g.baseCtx)
|
||||
}
|
||||
|
||||
// CancelAndWait cancels the context passed to any of the spawned goroutines and waits for all spawned
|
||||
// goroutines to exit.
|
||||
//
|
||||
// It is not safe to call Wait concurrently with any other method on g.
|
||||
func (g *Group) CancelAndWait() {
|
||||
g.finish()
|
||||
g.cancel()
|
||||
g.wg.Wait()
|
||||
g.resetCtx()
|
||||
}
|
||||
|
||||
// WaitToFinish will ends all periodic or polls routines. It will wait for
|
||||
// currently running functions to finish (cancel is not sent).
|
||||
//
|
||||
// It is not safe to call Wait concurrently with any other method on g.
|
||||
func (g *Group) WaitToFinish() {
|
||||
g.finish()
|
||||
g.wg.Wait()
|
||||
g.jobCtx, g.finish = context.WithCancel(g.baseCtx)
|
||||
}
|
||||
|
||||
func (g *Group) handlePanic() {
|
||||
if g.panicHandler != nil {
|
||||
g.panicHandler.HandlePanic()
|
||||
}
|
||||
}
|
||||
45
internal/bridge/api.go
Normal file
@ -0,0 +1,45 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"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,
|
||||
poolSize int,
|
||||
) []proton.Option {
|
||||
return []proton.Option{
|
||||
proton.WithHostURL(apiURL),
|
||||
proton.WithAppVersion(constants.AppVersion(version.Original())),
|
||||
proton.WithCookieJar(cookieJar),
|
||||
proton.WithTransport(transport),
|
||||
proton.WithAttPoolSize(poolSize),
|
||||
proton.WithLogger(logrus.StandardLogger()),
|
||||
}
|
||||
}
|
||||
38
internal/bridge/api_default.go
Normal file
@ -0,0 +1,38 @@
|
||||
// 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/>.
|
||||
|
||||
//go:build !build_qa
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"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,
|
||||
poolSize int,
|
||||
) []proton.Option {
|
||||
return defaultAPIOptions(apiURL, version, cookieJar, transport, poolSize)
|
||||
}
|
||||
53
internal/bridge/api_qa.go
Normal file
@ -0,0 +1,53 @@
|
||||
// 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/>.
|
||||
|
||||
//go:build build_qa
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"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,
|
||||
poolSize int,
|
||||
) []proton.Option {
|
||||
opt := defaultAPIOptions(apiURL, version, cookieJar, transport, poolSize)
|
||||
|
||||
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,152 +1,558 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// 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.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// 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 ProtonMail 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
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
logrus "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals]
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/gluon"
|
||||
imapEvents "github.com/ProtonMail/gluon/events"
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/gluon/watcher"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/async"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"github.com/emersion/go-smtp"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Bridge struct {
|
||||
*users.Users
|
||||
// vault holds bridge-specific data, such as preferences and known users (authorized or not).
|
||||
vault *vault.Vault
|
||||
|
||||
pref PreferenceProvider
|
||||
clientManager users.ClientManager
|
||||
// users holds authorized users.
|
||||
users map[string]*user.User
|
||||
usersLock safe.RWMutex
|
||||
|
||||
userAgentClientName string
|
||||
userAgentClientVersion string
|
||||
userAgentOS string
|
||||
// api manages user API clients.
|
||||
api *proton.Manager
|
||||
proxyCtl ProxyController
|
||||
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.
|
||||
imapServer *gluon.Server
|
||||
imapListener net.Listener
|
||||
imapEventCh chan imapEvents.Event
|
||||
|
||||
// smtpServer is the bridge's SMTP server.
|
||||
smtpServer *smtp.Server
|
||||
smtpListener net.Listener
|
||||
|
||||
// updater is the bridge's updater.
|
||||
updater Updater
|
||||
installCh chan installJob
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
// crashHandler
|
||||
crashHandler 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
|
||||
|
||||
// These control the bridge's IMAP and SMTP logging behaviour.
|
||||
logIMAPClient bool
|
||||
logIMAPServer bool
|
||||
logSMTP bool
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
func New(
|
||||
config Configer,
|
||||
pref PreferenceProvider,
|
||||
panicHandler users.PanicHandler,
|
||||
eventListener listener.Listener,
|
||||
clientManager users.ClientManager,
|
||||
credStorer users.CredentialsStorer,
|
||||
) *Bridge {
|
||||
// Allow DoH before starting the app if the user has previously set this setting.
|
||||
// This allows us to start even if protonmail is blocked.
|
||||
if pref.GetBool(preferences.AllowProxyKey) {
|
||||
clientManager.AllowProxy()
|
||||
// New creates a new bridge.
|
||||
func New( //nolint:funlen
|
||||
locator Locator, // the locator to provide paths to store data
|
||||
vault *vault.Vault, // the bridge's encrypted data store
|
||||
autostarter Autostarter, // the autostarter to manage autostart settings
|
||||
updater Updater, // the updater to fetch and install updates
|
||||
curVersion *semver.Version, // the current version of the bridge
|
||||
|
||||
apiURL string, // the URL of the API to use
|
||||
cookieJar http.CookieJar, // the cookie jar to use
|
||||
identifier Identifier, // the identifier to keep track of the user agent
|
||||
tlsReporter TLSReporter, // the TLS reporter to report TLS errors
|
||||
roundTripper http.RoundTripper, // the round tripper to use for API requests
|
||||
proxyCtl ProxyController, // the DoH controller
|
||||
crashHandler async.PanicHandler,
|
||||
reporter reporter.Reporter,
|
||||
|
||||
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, vault.SyncAttPool())...)
|
||||
|
||||
// tasks holds all the bridge's background tasks.
|
||||
tasks := async.NewGroup(context.Background(), crashHandler)
|
||||
|
||||
// imapEventCh forwards IMAP events from gluon instances to the bridge for processing.
|
||||
imapEventCh := make(chan imapEvents.Event)
|
||||
|
||||
// bridge is the bridge.
|
||||
bridge, err := newBridge(
|
||||
tasks,
|
||||
imapEventCh,
|
||||
|
||||
locator,
|
||||
vault,
|
||||
autostarter,
|
||||
updater,
|
||||
curVersion,
|
||||
crashHandler,
|
||||
reporter,
|
||||
|
||||
api,
|
||||
identifier,
|
||||
proxyCtl,
|
||||
logIMAPClient, logIMAPServer, logSMTP,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create bridge: %w", err)
|
||||
}
|
||||
|
||||
storeFactory := newStoreFactory(config, panicHandler, clientManager, eventListener)
|
||||
u := users.New(config, panicHandler, eventListener, clientManager, credStorer, storeFactory, true)
|
||||
b := &Bridge{
|
||||
Users: u,
|
||||
// Get an event channel for all events (individual events can be subscribed to later).
|
||||
eventCh, _ := bridge.GetEvents()
|
||||
|
||||
pref: pref,
|
||||
clientManager: clientManager,
|
||||
// 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)
|
||||
}
|
||||
|
||||
if pref.GetBool(preferences.FirstStartKey) {
|
||||
b.SendMetric(metrics.New(metrics.Setup, metrics.FirstStart, metrics.Label(config.GetVersion())))
|
||||
pref.SetBool(preferences.FirstStartKey, false)
|
||||
// Start serving IMAP.
|
||||
if err := bridge.serveIMAP(); err != nil {
|
||||
bridge.PushError(ErrServeIMAP)
|
||||
}
|
||||
|
||||
go b.heartbeat()
|
||||
// Start serving SMTP.
|
||||
if err := bridge.serveSMTP(); err != nil {
|
||||
bridge.PushError(ErrServeSMTP)
|
||||
}
|
||||
|
||||
return b
|
||||
return bridge, eventCh, nil
|
||||
}
|
||||
|
||||
// heartbeat sends a heartbeat signal once a day.
|
||||
func (b *Bridge) heartbeat() {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
// nolint:funlen
|
||||
func newBridge(
|
||||
tasks *async.Group,
|
||||
imapEventCh chan imapEvents.Event,
|
||||
|
||||
for range ticker.C {
|
||||
next, err := strconv.ParseInt(b.pref.Get(preferences.NextHeartbeatKey), 10, 64)
|
||||
locator Locator,
|
||||
vault *vault.Vault,
|
||||
autostarter Autostarter,
|
||||
updater Updater,
|
||||
curVersion *semver.Version,
|
||||
crashHandler async.PanicHandler,
|
||||
reporter reporter.Reporter,
|
||||
|
||||
api *proton.Manager,
|
||||
identifier Identifier,
|
||||
proxyCtl ProxyController,
|
||||
|
||||
logIMAPClient, logIMAPServer, logSMTP bool,
|
||||
) (*Bridge, error) {
|
||||
tlsConfig, err := loadTLSConfig(vault)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load TLS config: %w", err)
|
||||
}
|
||||
|
||||
gluonDir, err := getGluonDir(vault)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get Gluon directory: %w", err)
|
||||
}
|
||||
|
||||
imapServer, err := newIMAPServer(
|
||||
gluonDir,
|
||||
curVersion,
|
||||
tlsConfig,
|
||||
reporter,
|
||||
logIMAPClient,
|
||||
logIMAPServer,
|
||||
imapEventCh,
|
||||
tasks,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create IMAP server: %w", err)
|
||||
}
|
||||
|
||||
focusService, err := focus.NewService(curVersion)
|
||||
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,
|
||||
imapServer: imapServer,
|
||||
imapEventCh: imapEventCh,
|
||||
|
||||
updater: updater,
|
||||
installCh: make(chan installJob),
|
||||
|
||||
curVersion: curVersion,
|
||||
newVersion: curVersion,
|
||||
newVersionLock: safe.NewRWMutex(),
|
||||
|
||||
crashHandler: crashHandler,
|
||||
reporter: reporter,
|
||||
|
||||
focusService: focusService,
|
||||
autostarter: autostarter,
|
||||
locator: locator,
|
||||
|
||||
logIMAPClient: logIMAPClient,
|
||||
logIMAPServer: logIMAPServer,
|
||||
logSMTP: logSMTP,
|
||||
|
||||
tasks: tasks,
|
||||
}
|
||||
|
||||
bridge.smtpServer = newSMTPServer(bridge, tlsConfig, logSMTP)
|
||||
|
||||
return bridge, nil
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
// 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 lazy load users when triggered.
|
||||
bridge.goLoad = bridge.tasks.Trigger(func(ctx context.Context) {
|
||||
logrus.Info("Loading users")
|
||||
|
||||
if err := bridge.loadUsers(ctx); err != nil {
|
||||
logrus.WithError(err).Error("Failed to load users")
|
||||
} else {
|
||||
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 {
|
||||
continue
|
||||
bridge.publish(events.UpdateCheckFailed{Error: err})
|
||||
} else {
|
||||
bridge.handleUpdate(version)
|
||||
}
|
||||
nextTime := time.Unix(next, 0)
|
||||
if time.Now().After(nextTime) {
|
||||
b.SendMetric(metrics.New(metrics.Heartbeat, metrics.Daily, metrics.NoLabel))
|
||||
nextTime = nextTime.Add(24 * time.Hour)
|
||||
b.pref.Set(preferences.NextHeartbeatKey, strconv.FormatInt(nextTime.Unix(), 10))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
defer bridge.goUpdate()
|
||||
|
||||
// GetCurrentClient returns currently connected client (e.g. Thunderbird).
|
||||
func (b *Bridge) GetCurrentClient() string {
|
||||
res := b.userAgentClientName
|
||||
if b.userAgentClientVersion != "" {
|
||||
res = res + " " + b.userAgentClientVersion
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// SetCurrentClient updates client info (e.g. Thunderbird) and sets the user agent
|
||||
// on pmapi. By default no client is used, IMAP has to detect it on first login.
|
||||
func (b *Bridge) SetCurrentClient(clientName, clientVersion string) {
|
||||
b.userAgentClientName = clientName
|
||||
b.userAgentClientVersion = clientVersion
|
||||
b.updateUserAgent()
|
||||
}
|
||||
|
||||
// SetCurrentOS updates OS and sets the user agent on pmapi. By default we use
|
||||
// `runtime.GOOS`, but this can be overridden in case of better detection.
|
||||
func (b *Bridge) SetCurrentOS(os string) {
|
||||
b.userAgentOS = os
|
||||
b.updateUserAgent()
|
||||
}
|
||||
|
||||
func (b *Bridge) updateUserAgent() {
|
||||
b.clientManager.SetUserAgent(b.userAgentClientName, b.userAgentClientVersion, b.userAgentOS)
|
||||
}
|
||||
|
||||
// ReportBug reports a new bug from the user.
|
||||
func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error {
|
||||
c := b.clientManager.GetAnonymousClient()
|
||||
defer c.Logout()
|
||||
|
||||
title := "[Bridge] Bug"
|
||||
report := pmapi.ReportReq{
|
||||
OS: osType,
|
||||
OSVersion: osVersion,
|
||||
Browser: emailClient,
|
||||
Title: title,
|
||||
Description: description,
|
||||
Username: accountName,
|
||||
Email: address,
|
||||
}
|
||||
|
||||
if err := c.Report(report); err != nil {
|
||||
log.Error("Reporting bug failed: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Bug successfully reported")
|
||||
// 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")
|
||||
|
||||
// Close the IMAP server.
|
||||
if err := bridge.closeIMAP(ctx); err != nil {
|
||||
logrus.WithError(err).Error("Failed to close IMAP server")
|
||||
}
|
||||
|
||||
// Close the SMTP server.
|
||||
if err := bridge.closeSMTP(); err != nil {
|
||||
logrus.WithError(err).Error("Failed to close SMTP server")
|
||||
}
|
||||
|
||||
// Close all users.
|
||||
safe.RLock(func() {
|
||||
for _, user := range bridge.users {
|
||||
user.Close()
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
|
||||
// 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
|
||||
|
||||
// Save the last version of bridge that was run.
|
||||
if err := bridge.vault.SetLastVersion(bridge.curVersion); err != nil {
|
||||
logrus.WithError(err).Error("Failed to save last version")
|
||||
}
|
||||
}
|
||||
|
||||
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(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(ctx context.Context) {
|
||||
logrus.Info("Handling API status up")
|
||||
|
||||
safe.RLock(func() {
|
||||
for _, user := range bridge.users {
|
||||
user.OnStatusUp(ctx)
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
|
||||
bridge.goLoad()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) onStatusDown(ctx context.Context) {
|
||||
logrus.Info("Handling API status down")
|
||||
|
||||
safe.RLock(func() {
|
||||
for _, user := range bridge.users {
|
||||
user.OnStatusDown(ctx)
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
|
||||
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(), vault.GetBridgeTLSKey())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newListener(port int, useTLS bool, tlsConfig *tls.Config) (net.Listener, error) {
|
||||
if useTLS {
|
||||
tlsListener, err := tls.Listen("tcp", fmt.Sprintf("%v:%v", constants.Host, port), tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tlsListener, nil
|
||||
}
|
||||
|
||||
netListener, err := net.Listen("tcp", fmt.Sprintf("%v:%v", constants.Host, port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return netListener, nil
|
||||
}
|
||||
|
||||
func min(a, b time.Duration) time.Duration {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
644
internal/bridge/bridge_test.go
Normal file
@ -0,0 +1,644 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/go-proton-api/server"
|
||||
"github.com/ProtonMail/go-proton-api/server/backend"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/cookies"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"github.com/ProtonMail/proton-bridge/v3/tests"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
username = "username"
|
||||
password = []byte("password")
|
||||
|
||||
v2_3_0 = semver.MustParse("2.3.0")
|
||||
v2_4_0 = semver.MustParse("2.4.0")
|
||||
)
|
||||
|
||||
func init() {
|
||||
user.EventPeriod = 100 * time.Millisecond
|
||||
user.EventJitter = 0
|
||||
backend.GenerateKey = tests.FastGenerateKey
|
||||
certs.GenerateCert = tests.FastGenerateCert
|
||||
}
|
||||
|
||||
func TestBridge_ConnStatus(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Get a stream of connection status events.
|
||||
eventCh, done := bridge.GetEvents(events.ConnStatusUp{}, events.ConnStatusDown{})
|
||||
defer done()
|
||||
|
||||
// Simulate network disconnect.
|
||||
netCtl.Disable()
|
||||
|
||||
// Trigger some operation that will fail due to the network disconnect.
|
||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
// Wait for the event.
|
||||
require.Equal(t, events.ConnStatusDown{}, <-eventCh)
|
||||
|
||||
// Simulate network reconnect.
|
||||
netCtl.Enable()
|
||||
|
||||
// Trigger some operation that will succeed due to the network reconnect.
|
||||
userID, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, userID)
|
||||
|
||||
// Wait for the event.
|
||||
require.Equal(t, events.ConnStatusUp{}, <-eventCh)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_TLSIssue(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Get a stream of TLS issue events.
|
||||
tlsEventCh, done := bridge.GetEvents(events.TLSIssue{})
|
||||
defer done()
|
||||
|
||||
// Simulate a TLS issue.
|
||||
go func() {
|
||||
mocks.TLSIssueCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// Wait for the event.
|
||||
require.IsType(t, events.TLSIssue{}, <-tlsEventCh)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_Focus(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Get a stream of TLS issue events.
|
||||
raiseCh, done := bridge.GetEvents(events.Raise{})
|
||||
defer done()
|
||||
|
||||
// Simulate a focus event.
|
||||
focus.TryRaise()
|
||||
|
||||
// Wait for the event.
|
||||
require.IsType(t, events.Raise{}, <-raiseCh)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_UserAgent(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
var (
|
||||
calls []server.Call
|
||||
lock sync.Mutex
|
||||
)
|
||||
|
||||
s.AddCallWatcher(func(call server.Call) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
calls = append(calls, call)
|
||||
})
|
||||
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Set the platform to something other than the default.
|
||||
bridge.SetCurrentPlatform("platform")
|
||||
|
||||
// Assert that the user agent then contains the platform.
|
||||
require.Contains(t, bridge.GetCurrentUserAgent(), "platform")
|
||||
|
||||
// Login the user.
|
||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
// Assert that the user agent was sent to the API.
|
||||
require.Contains(t, calls[len(calls)-1].RequestHeader.Get("User-Agent"), bridge.GetCurrentUserAgent())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_Cookies(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
var (
|
||||
sessionIDs []string
|
||||
sessionIDsLock sync.RWMutex
|
||||
)
|
||||
|
||||
// Save any session IDs we use.
|
||||
s.AddCallWatcher(func(call server.Call) {
|
||||
cookie, err := (&http.Request{Header: call.RequestHeader}).Cookie("Session-Id")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sessionIDsLock.Lock()
|
||||
defer sessionIDsLock.Unlock()
|
||||
|
||||
sessionIDs = append(sessionIDs, cookie.Value)
|
||||
})
|
||||
|
||||
// Start bridge and add a user so that API assigns us a session ID via cookie.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Start bridge again and check that it uses the same session ID.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// ...
|
||||
})
|
||||
|
||||
// We should have used just one session ID.
|
||||
sessionIDsLock.Lock()
|
||||
defer sessionIDsLock.Unlock()
|
||||
|
||||
require.Len(t, xslices.Unique(sessionIDs), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_CheckUpdate(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Disable autoupdate for this test.
|
||||
require.NoError(t, bridge.SetAutoUpdate(false))
|
||||
|
||||
// Get a stream of update not available events.
|
||||
noUpdateCh, done := bridge.GetEvents(events.UpdateNotAvailable{})
|
||||
defer done()
|
||||
|
||||
// We are currently on the latest version.
|
||||
bridge.CheckForUpdates()
|
||||
|
||||
// we should receive an event indicating that no update is available.
|
||||
require.Equal(t, events.UpdateNotAvailable{}, <-noUpdateCh)
|
||||
|
||||
// Simulate a new version being available.
|
||||
mocks.Updater.SetLatestVersion(v2_4_0, v2_3_0)
|
||||
|
||||
// Get a stream of update available events.
|
||||
updateCh, done := bridge.GetEvents(events.UpdateAvailable{})
|
||||
defer done()
|
||||
|
||||
// Check for updates.
|
||||
bridge.CheckForUpdates()
|
||||
|
||||
// We should receive an event indicating that an update is available.
|
||||
require.Equal(t, events.UpdateAvailable{
|
||||
Version: updater.VersionInfo{
|
||||
Version: v2_4_0,
|
||||
MinAuto: v2_3_0,
|
||||
RolloutProportion: 1.0,
|
||||
},
|
||||
Silent: false,
|
||||
Compatible: true,
|
||||
}, <-updateCh)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_AutoUpdate(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Enable autoupdate for this test.
|
||||
require.NoError(t, bridge.SetAutoUpdate(true))
|
||||
|
||||
// Get a stream of update events.
|
||||
updateCh, done := bridge.GetEvents(events.UpdateInstalled{})
|
||||
defer done()
|
||||
|
||||
// Simulate a new version being available.
|
||||
mocks.Updater.SetLatestVersion(v2_4_0, v2_3_0)
|
||||
|
||||
// Check for updates.
|
||||
bridge.CheckForUpdates()
|
||||
|
||||
// We should receive an event indicating that the update was silently installed.
|
||||
require.Equal(t, events.UpdateInstalled{
|
||||
Version: updater.VersionInfo{
|
||||
Version: v2_4_0,
|
||||
MinAuto: v2_3_0,
|
||||
RolloutProportion: 1.0,
|
||||
},
|
||||
Silent: true,
|
||||
}, <-updateCh)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_ManualUpdate(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Disable autoupdate for this test.
|
||||
require.NoError(t, bridge.SetAutoUpdate(false))
|
||||
|
||||
// Get a stream of update available events.
|
||||
updateCh, done := bridge.GetEvents(events.UpdateAvailable{})
|
||||
defer done()
|
||||
|
||||
// Simulate a new version being available, but it's too new for us.
|
||||
mocks.Updater.SetLatestVersion(v2_4_0, v2_4_0)
|
||||
|
||||
// Check for updates.
|
||||
bridge.CheckForUpdates()
|
||||
|
||||
// We should receive an event indicating an update is available, but we can't install it.
|
||||
require.Equal(t, events.UpdateAvailable{
|
||||
Version: updater.VersionInfo{
|
||||
Version: v2_4_0,
|
||||
MinAuto: v2_4_0,
|
||||
RolloutProportion: 1.0,
|
||||
},
|
||||
Silent: false,
|
||||
Compatible: false,
|
||||
}, <-updateCh)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_ForceUpdate(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Get a stream of update events.
|
||||
updateCh, done := bridge.GetEvents(events.UpdateForced{})
|
||||
defer done()
|
||||
|
||||
// Set the minimum accepted app version to something newer than the current version.
|
||||
s.SetMinAppVersion(v2_4_0)
|
||||
|
||||
// Try to login the user. It will fail because the bridge is too old.
|
||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
// We should get an update required event.
|
||||
require.Equal(t, events.UpdateForced{}, <-updateCh)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_BadVaultKey(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
var userID string
|
||||
|
||||
// Login a user.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
newUserID, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
userID = newUserID
|
||||
})
|
||||
|
||||
// Start bridge with the correct vault key -- it should load the users correctly.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
require.ElementsMatch(t, []string{userID}, bridge.GetUserIDs())
|
||||
})
|
||||
|
||||
// Start bridge with a bad vault key, the vault will be wiped and bridge will show no users.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, []byte("bad"), func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
require.Empty(t, bridge.GetUserIDs())
|
||||
})
|
||||
|
||||
// Start bridge with a nil vault key, the vault will be wiped and bridge will show no users.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, nil, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
require.Empty(t, bridge.GetUserIDs())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_MissingGluonDir(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
var gluonDir string
|
||||
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Move the gluon dir.
|
||||
require.NoError(t, bridge.SetGluonDir(ctx, t.TempDir()))
|
||||
|
||||
// Get the gluon dir.
|
||||
gluonDir = bridge.GetGluonDir()
|
||||
})
|
||||
|
||||
// The user removes the gluon dir while bridge is not running.
|
||||
require.NoError(t, os.RemoveAll(gluonDir))
|
||||
|
||||
// Bridge starts but can't find the gluon dir; there should be no error.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// ...
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_AddressWithoutKeys(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
m := proton.New(
|
||||
proton.WithHostURL(s.GetHostURL()),
|
||||
proton.WithTransport(proton.InsecureTransport()),
|
||||
)
|
||||
defer m.Close()
|
||||
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Create a user which will have an address without keys.
|
||||
userID, _, err := s.CreateUser("nokeys", "nokeys@pm.me", []byte("password"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create an additional address for the user; it will not have keys.
|
||||
aliasAddrID, err := s.CreateAddress(userID, "alias@pm.me", []byte("password"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create an API client so we can remove the address keys.
|
||||
c, _, err := m.NewClientWithLogin(ctx, "nokeys", []byte("password"))
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
// Get the alias address.
|
||||
aliasAddr, err := c.GetAddress(ctx, aliasAddrID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Remove the address keys.
|
||||
require.NoError(t, s.RemoveAddressKey(userID, aliasAddrID, aliasAddr.Keys[0].ID))
|
||||
|
||||
// Watch for sync finished event.
|
||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||
defer done()
|
||||
|
||||
// We should be able to log the user in.
|
||||
require.NoError(t, getErr(bridge.LoginFull(context.Background(), "nokeys", []byte("password"), nil, nil)))
|
||||
require.NoError(t, err)
|
||||
|
||||
// The sync should eventually finish for the user without keys.
|
||||
require.Equal(t, userID, (<-syncCh).UserID)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_FactoryReset(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||
// The settings should be their default values.
|
||||
require.True(t, bridge.GetAutoUpdate())
|
||||
require.Equal(t, updater.StableChannel, bridge.GetUpdateChannel())
|
||||
|
||||
// Login the user.
|
||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Change some settings.
|
||||
require.NoError(t, bridge.SetAutoUpdate(false))
|
||||
require.NoError(t, bridge.SetUpdateChannel(updater.EarlyChannel))
|
||||
|
||||
// The user is now connected.
|
||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
||||
|
||||
// The settings should be changed.
|
||||
require.False(t, bridge.GetAutoUpdate())
|
||||
require.Equal(t, updater.EarlyChannel, bridge.GetUpdateChannel())
|
||||
|
||||
// Perform a factory reset.
|
||||
bridge.FactoryReset(ctx)
|
||||
|
||||
// The user is gone.
|
||||
require.Equal(t, []string{}, bridge.GetUserIDs())
|
||||
require.Equal(t, []string{}, getConnectedUserIDs(t, bridge))
|
||||
})
|
||||
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||
// The settings should be reset.
|
||||
require.True(t, bridge.GetAutoUpdate())
|
||||
require.Equal(t, updater.StableChannel, bridge.GetUpdateChannel())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_ChangeCacheDirectoryFailsBetweenDifferentVolumes(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("Test only necessary on windows")
|
||||
}
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Change directory
|
||||
err := bridge.SetGluonDir(ctx, "XX:\\")
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_ChangeCacheDirectory(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
newCacheDir := t.TempDir()
|
||||
currentCacheDir := bridge.GetGluonDir()
|
||||
|
||||
// Login the user.
|
||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The user is now connected.
|
||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
||||
|
||||
// Change directory
|
||||
err = bridge.SetGluonDir(ctx, newCacheDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = os.ReadDir(currentCacheDir)
|
||||
require.True(t, os.IsNotExist(err))
|
||||
|
||||
require.Equal(t, newCacheDir, bridge.GetGluonDir())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// withEnv creates the full test environment and runs the tests.
|
||||
func withEnv(t *testing.T, tests func(context.Context, *server.Server, *proton.NetCtl, bridge.Locator, []byte), opts ...server.Option) {
|
||||
server := server.New(opts...)
|
||||
defer server.Close()
|
||||
|
||||
// Add test user.
|
||||
_, _, err := server.CreateUser(username, username+"@pm.me", password)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Generate a random vault key.
|
||||
vaultKey, err := crypto.RandomToken(32)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a context used for the test.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Create a net controller so we can simulate network connectivity issues.
|
||||
netCtl := proton.NewNetCtl()
|
||||
|
||||
// Create a locations object to provide temporary locations for bridge data during the test.
|
||||
locations := locations.New(bridge.NewTestLocationsProvider(t.TempDir()), "config-name")
|
||||
|
||||
// Run the tests.
|
||||
tests(ctx, server, netCtl, locations, vaultKey)
|
||||
}
|
||||
|
||||
// withBridge creates a new bridge which points to the given API URL and uses the given keychain, and closes it when done.
|
||||
func withBridge(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
apiURL string,
|
||||
netCtl *proton.NetCtl,
|
||||
locator bridge.Locator,
|
||||
vaultKey []byte,
|
||||
tests func(*bridge.Bridge, *bridge.Mocks),
|
||||
) {
|
||||
// Create the mock objects used in the tests.
|
||||
mocks := bridge.NewMocks(t, v2_3_0, v2_3_0)
|
||||
defer mocks.Close()
|
||||
|
||||
// Bridge will enable the proxy by default at startup.
|
||||
mocks.ProxyCtl.EXPECT().AllowProxy()
|
||||
|
||||
// Get the path to the vault.
|
||||
vaultDir, err := locator.ProvideSettingsPath()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create the vault.
|
||||
vault, _, err := vault.New(vaultDir, t.TempDir(), vaultKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a new cookie jar.
|
||||
cookieJar, err := cookies.NewCookieJar(bridge.NewTestCookieJar(), vault)
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, cookieJar.PersistCookies()) }()
|
||||
|
||||
// Create a new bridge.
|
||||
bridge, eventCh, err := bridge.New(
|
||||
// The app stuff.
|
||||
locator,
|
||||
vault,
|
||||
mocks.Autostarter,
|
||||
mocks.Updater,
|
||||
v2_3_0,
|
||||
|
||||
// The API stuff.
|
||||
apiURL,
|
||||
cookieJar,
|
||||
useragent.New(),
|
||||
mocks.TLSReporter,
|
||||
proton.NewDialer(netCtl, &tls.Config{InsecureSkipVerify: true}).GetRoundTripper(),
|
||||
mocks.ProxyCtl,
|
||||
mocks.CrashHandler,
|
||||
mocks.Reporter,
|
||||
|
||||
// The logging stuff.
|
||||
os.Getenv("BRIDGE_LOG_IMAP_CLIENT") == "1",
|
||||
os.Getenv("BRIDGE_LOG_IMAP_SERVER") == "1",
|
||||
os.Getenv("BRIDGE_LOG_SMTP") == "1",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, bridge.GetErrors())
|
||||
|
||||
// Wait for bridge to finish loading users.
|
||||
waitForEvent(t, eventCh, events.AllUsersLoaded{})
|
||||
|
||||
// Set random IMAP and SMTP ports for the tests.
|
||||
require.NoError(t, bridge.SetIMAPPort(0))
|
||||
require.NoError(t, bridge.SetSMTPPort(0))
|
||||
|
||||
// Close the bridge when done.
|
||||
defer bridge.Close(ctx)
|
||||
|
||||
// Use the bridge.
|
||||
tests(bridge, mocks)
|
||||
}
|
||||
|
||||
func waitForEvent[T any](t *testing.T, eventCh <-chan events.Event, wantEvent T) {
|
||||
t.Helper()
|
||||
|
||||
for event := range eventCh {
|
||||
switch event.(type) { // nolint:gocritic
|
||||
case T:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// must is a helper function that panics on error.
|
||||
func must[T any](val T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func getConnectedUserIDs(t *testing.T, b *bridge.Bridge) []string {
|
||||
t.Helper()
|
||||
|
||||
return xslices.Filter(b.GetUserIDs(), func(userID string) bool {
|
||||
info, err := b.GetUserInfo(userID)
|
||||
require.NoError(t, err)
|
||||
|
||||
return info.State == bridge.Connected
|
||||
})
|
||||
}
|
||||
|
||||
func chToType[In, Out any](inCh <-chan In, done func()) (<-chan Out, func()) {
|
||||
outCh := make(chan Out)
|
||||
|
||||
go func() {
|
||||
defer close(outCh)
|
||||
|
||||
for in := range inCh {
|
||||
out, ok := any(in).(Out)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unexpected type %T", in))
|
||||
}
|
||||
|
||||
outCh <- out
|
||||
}
|
||||
}()
|
||||
|
||||
return outCh, done
|
||||
}
|
||||
233
internal/bridge/bug_report.go
Normal file
@ -0,0 +1,233 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxTotalAttachmentSize = 7 * (1 << 20)
|
||||
MaxCompressedFilesCount = 6
|
||||
)
|
||||
|
||||
func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, description, username, email, client string, attachLogs bool) error { //nolint:funlen
|
||||
var account string
|
||||
|
||||
if info, err := bridge.QueryUserInfo(username); err == nil {
|
||||
account = info.Username
|
||||
} else if userIDs := bridge.GetUserIDs(); len(userIDs) > 0 {
|
||||
if err := bridge.vault.GetUser(userIDs[0], func(user *vault.User) {
|
||||
account = user.Username()
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var atts []proton.ReportBugAttachment
|
||||
|
||||
if attachLogs {
|
||||
logs, err := getMatchingLogs(bridge.locator, func(filename string) bool {
|
||||
return logging.MatchLogName(filename) && !logging.MatchStackTraceName(filename)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
crashes, err := getMatchingLogs(bridge.locator, func(filename string) bool {
|
||||
return logging.MatchLogName(filename) && logging.MatchStackTraceName(filename)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
guiLogs, err := getMatchingLogs(bridge.locator, func(filename string) bool {
|
||||
return logging.MatchGUILogName(filename) && !logging.MatchStackTraceName(filename)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var matchFiles []string
|
||||
|
||||
// Include bridge logs, up to a maximum amount.
|
||||
matchFiles = append(matchFiles, logs[max(0, len(logs)-(MaxCompressedFilesCount/2)):]...)
|
||||
|
||||
// Include crash logs, up to a maximum amount.
|
||||
matchFiles = append(matchFiles, crashes[max(0, len(crashes)-(MaxCompressedFilesCount/2)):]...)
|
||||
|
||||
// bridge-gui keeps just one small (~ 1kb) log file; we always include it.
|
||||
if len(guiLogs) > 0 {
|
||||
matchFiles = append(matchFiles, guiLogs[len(guiLogs)-1])
|
||||
}
|
||||
|
||||
archive, err := zipFiles(matchFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
atts = append(atts, proton.ReportBugAttachment{
|
||||
Name: "logs.zip",
|
||||
Filename: "logs.zip",
|
||||
MIMEType: "application/zip",
|
||||
Body: body,
|
||||
})
|
||||
}
|
||||
|
||||
return bridge.api.ReportBug(ctx, proton.ReportBugReq{
|
||||
OS: osType,
|
||||
OSVersion: osVersion,
|
||||
|
||||
Title: "[Bridge] Bug",
|
||||
Description: description,
|
||||
|
||||
Client: client,
|
||||
ClientType: proton.ClientTypeEmail,
|
||||
ClientVersion: constants.AppVersion(bridge.curVersion.Original()),
|
||||
|
||||
Username: account,
|
||||
Email: email,
|
||||
}, atts...)
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func getMatchingLogs(locator Locator, filenameMatchFunc func(string) bool) (filenames []string, err error) {
|
||||
logsPath, err := locator.ProvideLogsPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files, err := os.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(MaxTotalAttachmentSize)
|
||||
|
||||
w := zip.NewWriter(buf)
|
||||
defer w.Close() //nolint:errcheck
|
||||
|
||||
for _, file := range filenames {
|
||||
if err := addFileToZip(file, w); 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
|
||||
}
|
||||
|
||||
if _, err := io.Copy(fileWriter, fileReader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fileReader.Close()
|
||||
}
|
||||
75
internal/bridge/configure.go
Normal file
@ -0,0 +1,75 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"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(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
|
||||
}
|
||||
|
||||
if address == "" {
|
||||
address = user.Emails()[0]
|
||||
}
|
||||
|
||||
username := address
|
||||
addresses := address
|
||||
|
||||
if user.GetAddressMode() == vault.CombinedMode {
|
||||
username = user.Emails()[0]
|
||||
addresses = strings.Join(user.Emails(), ",")
|
||||
}
|
||||
|
||||
if useragent.IsCatalinaOrNewer() && !bridge.vault.GetSMTPSSL() {
|
||||
if err := bridge.SetSMTPSSL(true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return (&clientconfig.AppleMail{}).Configure(
|
||||
constants.Host,
|
||||
bridge.vault.GetIMAPPort(),
|
||||
bridge.vault.GetSMTPPort(),
|
||||
bridge.vault.GetIMAPSSL(),
|
||||
bridge.vault.GetSMTPSSL(),
|
||||
username,
|
||||
addresses,
|
||||
user.BridgePass(),
|
||||
)
|
||||
}, bridge.usersLock)
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
// Copyright (c) 2020 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/>.
|
||||
|
||||
// Code generated by ./credits.sh at Wed Nov 4 13:57:47 CET 2020. DO NOT EDIT.
|
||||
|
||||
package bridge
|
||||
|
||||
const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/antlr/antlr4;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
|
||||
36
internal/bridge/errors.go
Normal file
@ -0,0 +1,36 @@
|
||||
// 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
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrVaultInsecure = errors.New("the vault is insecure")
|
||||
ErrVaultCorrupt = errors.New("the vault is corrupt")
|
||||
|
||||
ErrServeIMAP = errors.New("failed to serve IMAP")
|
||||
ErrServeSMTP = errors.New("failed to serve SMTP")
|
||||
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")
|
||||
)
|
||||
64
internal/bridge/files.go
Normal file
@ -0,0 +1,64 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func moveDir(from, to string) error {
|
||||
entries, err := os.ReadDir(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
if err := os.Mkdir(filepath.Join(to, entry.Name()), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := moveDir(filepath.Join(from, entry.Name()), filepath.Join(to, entry.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(filepath.Join(from, entry.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := moveFile(filepath.Join(from, entry.Name()), filepath.Join(to, entry.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return os.Remove(from)
|
||||
}
|
||||
|
||||
func moveFile(from, to string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(to), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Rename(from, to); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
73
internal/bridge/files_test.go
Normal file
@ -0,0 +1,73 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMoveDir(t *testing.T) {
|
||||
from, to := t.TempDir(), t.TempDir()
|
||||
|
||||
// Create some files in from.
|
||||
if err := os.WriteFile(filepath.Join(from, "a"), []byte("a"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(from, "b"), []byte("b"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(from, "c"), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(from, "c", "d"), []byte("d"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Move the files.
|
||||
if err := moveDir(from, to); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check that the files were moved.
|
||||
if _, err := os.Stat(filepath.Join(from, "a")); !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(to, "a")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(from, "b")); !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(to, "b")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(from, "c")); !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(to, "c")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(from, "c", "d")); !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(to, "c", "d")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
26
internal/bridge/identifier.go
Normal file
@ -0,0 +1,26 @@
|
||||
// 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
|
||||
|
||||
func (bridge *Bridge) GetCurrentUserAgent() string {
|
||||
return bridge.identifier.GetUserAgent()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetCurrentPlatform(platform string) {
|
||||
bridge.identifier.SetPlatform(platform)
|
||||
}
|
||||
331
internal/bridge/imap.go
Normal file
@ -0,0 +1,331 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/gluon"
|
||||
imapEvents "github.com/ProtonMail/gluon/events"
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/gluon/store"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/async"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultClientName = "UnknownClient"
|
||||
defaultClientVersion = "0.0.1"
|
||||
)
|
||||
|
||||
func (bridge *Bridge) serveIMAP() error {
|
||||
if bridge.imapServer == nil {
|
||||
return fmt.Errorf("no imap server instance running")
|
||||
}
|
||||
|
||||
logrus.Info("Starting IMAP server")
|
||||
|
||||
imapListener, err := newListener(bridge.vault.GetIMAPPort(), bridge.vault.GetIMAPSSL(), bridge.tlsConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create IMAP listener: %w", err)
|
||||
}
|
||||
|
||||
bridge.imapListener = imapListener
|
||||
|
||||
if err := bridge.imapServer.Serve(context.Background(), bridge.imapListener); err != nil {
|
||||
return fmt.Errorf("failed to serve IMAP: %w", err)
|
||||
}
|
||||
|
||||
if err := bridge.vault.SetIMAPPort(getPort(imapListener.Addr())); err != nil {
|
||||
return fmt.Errorf("failed to set IMAP port: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bridge *Bridge) restartIMAP() error {
|
||||
logrus.Info("Restarting IMAP server")
|
||||
|
||||
if bridge.imapListener != nil {
|
||||
if err := bridge.imapListener.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close IMAP listener: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return bridge.serveIMAP()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) closeIMAP(ctx context.Context) error {
|
||||
logrus.Info("Closing IMAP server")
|
||||
|
||||
if bridge.imapServer != nil {
|
||||
if err := bridge.imapServer.Close(ctx); err != nil {
|
||||
return fmt.Errorf("failed to close IMAP server: %w", err)
|
||||
}
|
||||
bridge.imapServer = nil
|
||||
}
|
||||
|
||||
if bridge.imapListener != nil {
|
||||
if err := bridge.imapListener.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close IMAP listener: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addIMAPUser connects the given user to gluon.
|
||||
func (bridge *Bridge) addIMAPUser(ctx context.Context, user *user.User) error {
|
||||
if bridge.imapServer == nil {
|
||||
return fmt.Errorf("no imap server instance running")
|
||||
}
|
||||
|
||||
imapConn, err := user.NewIMAPConnectors()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create IMAP connectors: %w", err)
|
||||
}
|
||||
|
||||
for addrID, imapConn := range imapConn {
|
||||
log := logrus.WithFields(logrus.Fields{
|
||||
"userID": user.ID(),
|
||||
"addrID": addrID,
|
||||
})
|
||||
|
||||
if gluonID, ok := user.GetGluonID(addrID); ok {
|
||||
log.WithField("gluonID", gluonID).Info("Loading existing IMAP user")
|
||||
|
||||
if err := bridge.imapServer.LoadUser(ctx, imapConn, gluonID, user.GluonKey()); err != nil {
|
||||
return fmt.Errorf("failed to load IMAP user: %w", err)
|
||||
}
|
||||
} else {
|
||||
log.Info("Creating new IMAP user")
|
||||
|
||||
gluonID, err := bridge.imapServer.AddUser(ctx, imapConn, user.GluonKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add IMAP user: %w", err)
|
||||
}
|
||||
|
||||
if err := user.SetGluonID(addrID, gluonID); err != nil {
|
||||
return fmt.Errorf("failed to set IMAP user ID: %w", err)
|
||||
}
|
||||
|
||||
log.WithField("gluonID", gluonID).Info("Created new IMAP user")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeIMAPUser disconnects the given user from gluon, optionally also removing its files.
|
||||
func (bridge *Bridge) removeIMAPUser(ctx context.Context, user *user.User, withData bool) error {
|
||||
if bridge.imapServer == nil {
|
||||
return fmt.Errorf("no imap server instance running")
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"userID": user.ID(),
|
||||
"withData": withData,
|
||||
}).Debug("Removing IMAP user")
|
||||
|
||||
for addrID, gluonID := range user.GetGluonIDs() {
|
||||
if err := bridge.imapServer.RemoveUser(ctx, gluonID, withData); err != nil {
|
||||
return fmt.Errorf("failed to remove IMAP user: %w", err)
|
||||
}
|
||||
|
||||
if withData {
|
||||
if err := user.RemoveGluonID(addrID, gluonID); err != nil {
|
||||
return fmt.Errorf("failed to remove IMAP user ID: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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.SessionAdded:
|
||||
if !bridge.identifier.HasClient() {
|
||||
bridge.identifier.SetClient(defaultClientName, defaultClientVersion)
|
||||
}
|
||||
|
||||
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.identifier.SetClient(event.IMAPID.Name, event.IMAPID.Version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getGluonDir(encVault *vault.Vault) (string, error) {
|
||||
empty, exists, err := isEmpty(encVault.GetGluonDir())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to check if gluon dir is empty: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
if err := os.MkdirAll(encVault.GetGluonDir(), 0o700); err != nil {
|
||||
return "", fmt.Errorf("failed to create gluon dir: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if empty {
|
||||
if err := encVault.ForUser(runtime.NumCPU(), func(user *vault.User) error {
|
||||
return user.ClearSyncStatus()
|
||||
}); err != nil {
|
||||
return "", fmt.Errorf("failed to reset user sync status: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return encVault.GetGluonDir(), nil
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
func newIMAPServer(
|
||||
gluonDir string,
|
||||
version *semver.Version,
|
||||
tlsConfig *tls.Config,
|
||||
reporter reporter.Reporter,
|
||||
logClient, logServer bool,
|
||||
eventCh chan<- imapEvents.Event,
|
||||
tasks *async.Group,
|
||||
) (*gluon.Server, error) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"gluonDir": gluonDir,
|
||||
"version": version,
|
||||
"logClient": logClient,
|
||||
"logServer": logServer,
|
||||
}).Info("Creating IMAP server")
|
||||
|
||||
if logClient || logServer {
|
||||
log := logrus.WithField("protocol", "IMAP")
|
||||
log.Warning("================================================")
|
||||
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
|
||||
log.Warning("================================================")
|
||||
}
|
||||
|
||||
var imapClientLog io.Writer
|
||||
|
||||
if logClient {
|
||||
imapClientLog = logging.NewIMAPLogger()
|
||||
} else {
|
||||
imapClientLog = io.Discard
|
||||
}
|
||||
|
||||
var imapServerLog io.Writer
|
||||
|
||||
if logServer {
|
||||
imapServerLog = logging.NewIMAPLogger()
|
||||
} else {
|
||||
imapServerLog = io.Discard
|
||||
}
|
||||
|
||||
imapServer, err := gluon.New(
|
||||
gluon.WithTLS(tlsConfig),
|
||||
gluon.WithDataDir(gluonDir),
|
||||
gluon.WithStoreBuilder(new(storeBuilder)),
|
||||
gluon.WithLogger(imapClientLog, imapServerLog),
|
||||
getGluonVersionInfo(version),
|
||||
gluon.WithReporter(reporter),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tasks.Once(func(ctx context.Context) {
|
||||
async.ForwardContext(ctx, eventCh, imapServer.AddWatcher())
|
||||
})
|
||||
|
||||
tasks.Once(func(ctx context.Context) {
|
||||
async.RangeContext(ctx, imapServer.GetErrorCh(), func(err error) {
|
||||
logrus.WithError(err).Error("IMAP server error")
|
||||
})
|
||||
})
|
||||
|
||||
return imapServer, nil
|
||||
}
|
||||
|
||||
func getGluonVersionInfo(version *semver.Version) gluon.Option {
|
||||
return gluon.WithVersionInfo(
|
||||
int(version.Major()),
|
||||
int(version.Minor()),
|
||||
int(version.Patch()),
|
||||
constants.FullAppName,
|
||||
"TODO",
|
||||
"TODO",
|
||||
)
|
||||
}
|
||||
|
||||
// isEmpty returns whether the given directory is empty.
|
||||
// If the directory does not exist, the second return value is false.
|
||||
func isEmpty(dir string) (bool, bool, error) {
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return false, false, fmt.Errorf("failed to stat %s: %w", dir, err)
|
||||
}
|
||||
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return false, false, fmt.Errorf("failed to read dir %s: %w", dir, err)
|
||||
}
|
||||
|
||||
return len(entries) == 0, true, nil
|
||||
}
|
||||
|
||||
type storeBuilder struct{}
|
||||
|
||||
func (*storeBuilder) New(path, userID string, passphrase []byte) (store.Store, error) {
|
||||
return store.NewOnDiskStore(
|
||||
filepath.Join(path, userID),
|
||||
passphrase,
|
||||
store.WithCompressor(new(store.GZipCompressor)),
|
||||
)
|
||||
}
|
||||
|
||||
func (*storeBuilder) Delete(path, userID string) error {
|
||||
return os.RemoveAll(filepath.Join(path, userID))
|
||||
}
|
||||
30
internal/bridge/locations.go
Normal file
@ -0,0 +1,30 @@
|
||||
// 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
|
||||
|
||||
func (bridge *Bridge) GetLogsPath() (string, error) {
|
||||
return bridge.locator.ProvideLogsPath()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetLicenseFilePath() string {
|
||||
return bridge.locator.GetLicenseFilePath()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetDependencyLicensesLink() string {
|
||||
return bridge.locator.GetDependencyLicensesLink()
|
||||
}
|
||||
36
internal/bridge/main_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
// 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_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())
|
||||
}
|
||||
151
internal/bridge/mocks.go
Normal file
@ -0,0 +1,151 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
// 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 he end of any go-routine:
|
||||
mocks.CrashHandler.EXPECT().HandlePanic().AnyTimes()
|
||||
|
||||
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(ctx context.Context, downloader updater.Downloader, channel updater.Channel) (updater.VersionInfo, error) {
|
||||
testUpdater.lock.RLock()
|
||||
defer testUpdater.lock.RUnlock()
|
||||
|
||||
return testUpdater.latest, nil
|
||||
}
|
||||
|
||||
func (testUpdater *TestUpdater) InstallUpdate(ctx context.Context, downloader updater.Downloader, update updater.VersionInfo) 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/proton-bridge/v3/internal/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() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "HandlePanic")
|
||||
}
|
||||
|
||||
// HandlePanic indicates an expected call of HandlePanic.
|
||||
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
|
||||
}
|
||||
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) 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 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"
|
||||
}
|
||||
146
internal/bridge/mocks/mocks.go
Normal file
@ -0,0 +1,146 @@
|
||||
// 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))
|
||||
}
|
||||
113
internal/bridge/refresh_test.go
Normal file
@ -0,0 +1,113 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"github.com/bradenaw/juniper/iterator"
|
||||
"github.com/emersion/go-imap/client"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBridge_Refresh(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
userID, _, err := s.CreateUser("imap", "imap@pm.me", password)
|
||||
require.NoError(t, err)
|
||||
|
||||
names := iterator.Collect(iterator.Map(iterator.Counter(10), func(i int) string {
|
||||
return fmt.Sprintf("folder%v", i)
|
||||
}))
|
||||
|
||||
for _, name := range names {
|
||||
must(s.CreateLabel(userID, name, "", proton.LabelTypeFolder))
|
||||
}
|
||||
|
||||
// The initial user should be fully synced.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||
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)
|
||||
})
|
||||
|
||||
// If we then connect an IMAP client, it should see all the labels with UID validity of 1.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
|
||||
info, err := b.GetUserInfo(userID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, info.State == bridge.Connected)
|
||||
|
||||
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass)))
|
||||
defer func() { _ = client.Logout() }()
|
||||
|
||||
for _, name := range names {
|
||||
status, err := client.Select("Folders/"+name, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint32(1), status.UidValidity)
|
||||
}
|
||||
})
|
||||
|
||||
// Refresh the user; this will force a resync.
|
||||
require.NoError(t, s.RefreshUser(userID, proton.RefreshAll))
|
||||
|
||||
// If we then connect an IMAP client, it should see all the labels with UID validity of 1.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
|
||||
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||
defer done()
|
||||
|
||||
require.Equal(t, userID, (<-syncCh).UserID)
|
||||
})
|
||||
|
||||
// After resync, the IMAP client should see all the labels with UID validity of 2.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
|
||||
info, err := b.GetUserInfo(userID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, info.State == bridge.Connected)
|
||||
|
||||
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass)))
|
||||
defer func() { _ = client.Logout() }()
|
||||
|
||||
for _, name := range names {
|
||||
status, err := client.Select("Folders/"+name, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint32(2), status.UidValidity)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
// Copyright (c) 2020 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/>.
|
||||
|
||||
// Code generated by ./release-notes.sh at 'Wed Nov 4 12:24:35 PM CET 2020'. DO NOT EDIT.
|
||||
|
||||
package bridge
|
||||
|
||||
const ReleaseNotes = `• Ensured better message flow by refactoring both address and date parsing
|
||||
• Improved secure connectivity checks
|
||||
• Better deb packaging
|
||||
• More robust error handling
|
||||
`
|
||||
|
||||
const ReleaseFixedBugs = `• Ensured that conversations are properly threaded
|
||||
• Fixed Linux font issues (Fedora)
|
||||
• Better handling of Mime encrypted messages
|
||||
`
|
||||
115
internal/bridge/send_test.go
Normal file
@ -0,0 +1,115 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/client"
|
||||
"github.com/emersion/go-sasl"
|
||||
"github.com/emersion/go-smtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBridge_Send(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
_, _, err := s.CreateUser("recipient", "recipient@pm.me", password)
|
||||
require.NoError(t, err)
|
||||
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
senderUserID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
recipientUserID, err := bridge.LoginFull(ctx, "recipient", password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
senderInfo, err := bridge.GetUserInfo(senderUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
recipientInfo, err := bridge.GetUserInfo(recipientUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
// Dial the server.
|
||||
client, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
||||
require.NoError(t, err)
|
||||
defer client.Close() //nolint:errcheck
|
||||
|
||||
// Upgrade to TLS.
|
||||
require.NoError(t, client.StartTLS(&tls.Config{InsecureSkipVerify: true}))
|
||||
|
||||
if i%2 == 0 {
|
||||
// Authorize with SASL PLAIN.
|
||||
require.NoError(t, client.Auth(sasl.NewPlainClient(
|
||||
senderInfo.Addresses[0],
|
||||
senderInfo.Addresses[0],
|
||||
string(senderInfo.BridgePass)),
|
||||
))
|
||||
} else {
|
||||
// Authorize with SASL LOGIN.
|
||||
require.NoError(t, client.Auth(sasl.NewLoginClient(
|
||||
senderInfo.Addresses[0],
|
||||
string(senderInfo.BridgePass)),
|
||||
))
|
||||
}
|
||||
|
||||
// Send the message.
|
||||
require.NoError(t, client.SendMail(
|
||||
senderInfo.Addresses[0],
|
||||
[]string{recipientInfo.Addresses[0]},
|
||||
strings.NewReader(fmt.Sprintf("Subject: Test %v\r\n\r\nHello world!", i)),
|
||||
))
|
||||
}
|
||||
|
||||
// Connect the sender IMAP client.
|
||||
senderIMAPClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, senderIMAPClient.Login(senderInfo.Addresses[0], string(senderInfo.BridgePass)))
|
||||
defer senderIMAPClient.Logout() //nolint:errcheck
|
||||
|
||||
// Connect the recipient IMAP client.
|
||||
recipientIMAPClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, recipientIMAPClient.Login(recipientInfo.Addresses[0], string(recipientInfo.BridgePass)))
|
||||
defer recipientIMAPClient.Logout() //nolint:errcheck
|
||||
|
||||
// Sender should have 10 messages in the sent folder.
|
||||
// Recipient should have 0 messages in inbox.
|
||||
require.Eventually(t, func() bool {
|
||||
sent, err := senderIMAPClient.Status(`Sent`, []imap.StatusItem{imap.StatusMessages})
|
||||
require.NoError(t, err)
|
||||
|
||||
inbox, err := recipientIMAPClient.Status(`Inbox`, []imap.StatusItem{imap.StatusMessages})
|
||||
require.NoError(t, err)
|
||||
|
||||
return sent.Messages == 10 && inbox.Messages == 10
|
||||
}, 10*time.Second, 100*time.Millisecond)
|
||||
})
|
||||
})
|
||||
}
|
||||
78
internal/bridge/sentry_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/gluon/liner"
|
||||
"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"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBridge_Report(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||
defer done()
|
||||
|
||||
// Log in the user.
|
||||
userID, err := b.LoginFull(ctx, username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wait until the sync has finished.
|
||||
require.Equal(t, userID, (<-syncCh).UserID)
|
||||
|
||||
// Get the IMAP info.
|
||||
info, err := b.GetUserInfo(userID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, info.State == bridge.Connected)
|
||||
|
||||
// Dial the IMAP port.
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, conn.Close()) }()
|
||||
|
||||
// Sending garbage to the IMAP port should cause the bridge to report it.
|
||||
mocks.Reporter.EXPECT().ReportMessageWithContext(
|
||||
gomock.Eq("Failed to parse IMAP command"),
|
||||
gomock.Any(),
|
||||
).Return(nil)
|
||||
|
||||
// Read lines from the IMAP port.
|
||||
lineCh := liner.New(conn).Lines(func() error { return nil })
|
||||
|
||||
// On connection, we should get the greeting.
|
||||
require.Contains(t, string((<-lineCh).Line), "* OK")
|
||||
|
||||
// Send garbage data.
|
||||
must(conn.Write([]byte("tag garbage\r\n")))
|
||||
|
||||
// Bridge will reply with BAD.
|
||||
require.Contains(t, string((<-lineCh).Line), "tag BAD")
|
||||
})
|
||||
})
|
||||
}
|
||||
338
internal/bridge/settings.go
Normal file
@ -0,0 +1,338 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (bridge *Bridge) GetKeychainApp() (string, error) {
|
||||
vaultDir, err := bridge.locator.ProvideSettingsPath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return vault.GetHelper(vaultDir)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetKeychainApp(helper string) error {
|
||||
vaultDir, err := bridge.locator.ProvideSettingsPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return vault.SetHelper(vaultDir, helper)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetIMAPPort() int {
|
||||
return bridge.vault.GetIMAPPort()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetIMAPPort(newPort int) error {
|
||||
if newPort == bridge.vault.GetIMAPPort() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := bridge.vault.SetIMAPPort(newPort); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bridge.restartIMAP()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetIMAPSSL() bool {
|
||||
return bridge.vault.GetIMAPSSL()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetIMAPSSL(newSSL bool) error {
|
||||
if newSSL == bridge.vault.GetIMAPSSL() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := bridge.vault.SetIMAPSSL(newSSL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bridge.restartIMAP()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetSMTPPort() int {
|
||||
return bridge.vault.GetSMTPPort()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetSMTPPort(newPort int) error {
|
||||
if newPort == bridge.vault.GetSMTPPort() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := bridge.vault.SetSMTPPort(newPort); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bridge.restartSMTP()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetSMTPSSL() bool {
|
||||
return bridge.vault.GetSMTPSSL()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetSMTPSSL(newSSL bool) error {
|
||||
if newSSL == bridge.vault.GetSMTPSSL() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := bridge.vault.SetSMTPSSL(newSSL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bridge.restartSMTP()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetGluonDir() string {
|
||||
return bridge.vault.GetGluonDir()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error {
|
||||
return safe.RLockRet(func() error {
|
||||
currentGluonDir := bridge.GetGluonDir()
|
||||
if newGluonDir == currentGluonDir {
|
||||
return fmt.Errorf("new gluon dir is the same as the old one")
|
||||
}
|
||||
|
||||
currentVolumeName := filepath.VolumeName(currentGluonDir)
|
||||
newVolumeName := filepath.VolumeName(newGluonDir)
|
||||
|
||||
if currentVolumeName != newVolumeName {
|
||||
return fmt.Errorf("it's currently not possible to move the cache between different volumes")
|
||||
}
|
||||
|
||||
if err := bridge.closeIMAP(context.Background()); err != nil {
|
||||
return fmt.Errorf("failed to close IMAP: %w", err)
|
||||
}
|
||||
|
||||
if err := moveDir(bridge.GetGluonDir(), newGluonDir); err != nil {
|
||||
return fmt.Errorf("failed to move gluon dir: %w", err)
|
||||
}
|
||||
|
||||
if err := bridge.vault.SetGluonDir(newGluonDir); err != nil {
|
||||
return fmt.Errorf("failed to set new gluon dir: %w", err)
|
||||
}
|
||||
|
||||
imapServer, err := newIMAPServer(
|
||||
bridge.vault.GetGluonDir(),
|
||||
bridge.curVersion,
|
||||
bridge.tlsConfig,
|
||||
bridge.reporter,
|
||||
bridge.logIMAPClient,
|
||||
bridge.logIMAPServer,
|
||||
bridge.imapEventCh,
|
||||
bridge.tasks,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new IMAP server: %w", err)
|
||||
}
|
||||
|
||||
bridge.imapServer = imapServer
|
||||
|
||||
for _, user := range bridge.users {
|
||||
if err := bridge.addIMAPUser(ctx, user); err != nil {
|
||||
return fmt.Errorf("failed to add users to new IMAP server: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := bridge.serveIMAP(); err != nil {
|
||||
return fmt.Errorf("failed to serve IMAP: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, bridge.usersLock)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetProxyAllowed() bool {
|
||||
return bridge.vault.GetProxyAllowed()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetProxyAllowed(allowed bool) error {
|
||||
if allowed {
|
||||
bridge.proxyCtl.AllowProxy()
|
||||
} else {
|
||||
bridge.proxyCtl.DisallowProxy()
|
||||
}
|
||||
|
||||
return bridge.vault.SetProxyAllowed(allowed)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetShowAllMail() bool {
|
||||
return bridge.vault.GetShowAllMail()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetShowAllMail(show bool) error {
|
||||
return safe.RLockRet(func() error {
|
||||
for _, user := range bridge.users {
|
||||
user.SetShowAllMail(show)
|
||||
}
|
||||
|
||||
return bridge.vault.SetShowAllMail(show)
|
||||
}, bridge.usersLock)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetAutostart() bool {
|
||||
return bridge.vault.GetAutostart()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetAutostart(autostart bool) error {
|
||||
if err := bridge.vault.SetAutostart(autostart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if autostart {
|
||||
err = bridge.autostarter.Enable()
|
||||
} else {
|
||||
err = bridge.autostarter.Disable()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetAutoUpdate() bool {
|
||||
return bridge.vault.GetAutoUpdate()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetAutoUpdate(autoUpdate bool) error {
|
||||
if bridge.vault.GetAutoUpdate() == autoUpdate {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := bridge.vault.SetAutoUpdate(autoUpdate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bridge.goUpdate()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetUpdateChannel() updater.Channel {
|
||||
return bridge.vault.GetUpdateChannel()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetUpdateChannel(channel updater.Channel) error {
|
||||
if bridge.vault.GetUpdateChannel() == channel {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := bridge.vault.SetUpdateChannel(channel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bridge.goUpdate()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetCurrentVersion() *semver.Version {
|
||||
return bridge.curVersion
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetLastVersion() *semver.Version {
|
||||
return bridge.vault.GetLastVersion()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetFirstStart() bool {
|
||||
return bridge.vault.GetFirstStart()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetFirstStart(firstStart bool) error {
|
||||
return bridge.vault.SetFirstStart(firstStart)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetFirstStartGUI() bool {
|
||||
return bridge.vault.GetFirstStartGUI()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetFirstStartGUI(firstStart bool) error {
|
||||
return bridge.vault.SetFirstStartGUI(firstStart)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetColorScheme() string {
|
||||
return bridge.vault.GetColorScheme()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetColorScheme(colorScheme string) error {
|
||||
return bridge.vault.SetColorScheme(colorScheme)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) FactoryReset(ctx context.Context) {
|
||||
// Delete all the users.
|
||||
safe.Lock(func() {
|
||||
for _, user := range bridge.users {
|
||||
bridge.logoutUser(ctx, user, true, true)
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
|
||||
// Wipe the vault.
|
||||
gluonDir, err := bridge.locator.ProvideGluonPath()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("Failed to provide gluon dir")
|
||||
} else if err := bridge.vault.Reset(gluonDir); err != nil {
|
||||
logrus.WithError(err).Error("Failed to reset vault")
|
||||
}
|
||||
|
||||
// Then delete all files.
|
||||
if err := bridge.locator.Clear(); err != nil {
|
||||
logrus.WithError(err).Error("Failed to clear data paths")
|
||||
}
|
||||
|
||||
// Lastly clear the keychain.
|
||||
vaultDir, err := bridge.locator.ProvideSettingsPath()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("Failed to get vault dir")
|
||||
} else if helper, err := vault.GetHelper(vaultDir); err != nil {
|
||||
logrus.WithError(err).Error("Failed to get keychain helper")
|
||||
} else if keychain, err := keychain.NewKeychain(helper, constants.KeyChainName); err != nil {
|
||||
logrus.WithError(err).Error("Failed to get keychain")
|
||||
} else if err := keychain.Clear(); err != nil {
|
||||
logrus.WithError(err).Error("Failed to clear keychain")
|
||||
}
|
||||
}
|
||||
|
||||
func getPort(addr net.Addr) int {
|
||||
switch addr := addr.(type) {
|
||||
case *net.TCPAddr:
|
||||
return addr.Port
|
||||
|
||||
case *net.UDPAddr:
|
||||
return addr.Port
|
||||
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
178
internal/bridge/settings_test.go
Normal file
@ -0,0 +1,178 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/go-proton-api/server"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBridge_Settings_GluonDir(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Create a user.
|
||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a new location for the Gluon data.
|
||||
newGluonDir := t.TempDir()
|
||||
|
||||
// Move the gluon dir; it should also move the user's data.
|
||||
require.NoError(t, bridge.SetGluonDir(context.Background(), newGluonDir))
|
||||
|
||||
// Check that the new directory is not empty.
|
||||
entries, err := os.ReadDir(newGluonDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
// There should be at least one entry.
|
||||
require.NotEmpty(t, entries)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_Settings_IMAPPort(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
curPort := bridge.GetIMAPPort()
|
||||
|
||||
// Set the port to 1144.
|
||||
require.NoError(t, bridge.SetIMAPPort(1144))
|
||||
|
||||
// Get the new setting.
|
||||
require.Equal(t, 1144, bridge.GetIMAPPort())
|
||||
|
||||
// Assert that it has changed.
|
||||
require.NotEqual(t, curPort, bridge.GetIMAPPort())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_Settings_IMAPSSL(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// By default, IMAP SSL is disabled.
|
||||
require.False(t, bridge.GetIMAPSSL())
|
||||
|
||||
// Enable IMAP SSL.
|
||||
require.NoError(t, bridge.SetIMAPSSL(true))
|
||||
|
||||
// Get the new setting.
|
||||
require.True(t, bridge.GetIMAPSSL())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_Settings_SMTPPort(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
curPort := bridge.GetSMTPPort()
|
||||
|
||||
// Set the port to 1024.
|
||||
require.NoError(t, bridge.SetSMTPPort(1024))
|
||||
|
||||
// Get the new setting.
|
||||
require.Equal(t, 1024, bridge.GetSMTPPort())
|
||||
|
||||
// Assert that it has changed.
|
||||
require.NotEqual(t, curPort, bridge.GetSMTPPort())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_Settings_SMTPSSL(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// By default, SMTP SSL is disabled.
|
||||
require.False(t, bridge.GetSMTPSSL())
|
||||
|
||||
// Enable SMTP SSL.
|
||||
require.NoError(t, bridge.SetSMTPSSL(true))
|
||||
|
||||
// Get the new setting.
|
||||
require.True(t, bridge.GetSMTPSSL())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_Settings_Proxy(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// By default, proxy is allowed.
|
||||
require.True(t, bridge.GetProxyAllowed())
|
||||
|
||||
// Disallow proxy.
|
||||
mocks.ProxyCtl.EXPECT().DisallowProxy()
|
||||
require.NoError(t, bridge.SetProxyAllowed(false))
|
||||
|
||||
// Get the new setting.
|
||||
require.False(t, bridge.GetProxyAllowed())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_Settings_Autostart(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// By default, autostart is disabled.
|
||||
require.False(t, bridge.GetAutostart())
|
||||
|
||||
// Enable autostart.
|
||||
mocks.Autostarter.EXPECT().Enable().Return(nil)
|
||||
require.NoError(t, bridge.SetAutostart(true))
|
||||
|
||||
// Get the new setting.
|
||||
require.True(t, bridge.GetAutostart())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_Settings_FirstStart(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// By default, first start is true.
|
||||
require.True(t, bridge.GetFirstStart())
|
||||
|
||||
// Set first start to false.
|
||||
require.NoError(t, bridge.SetFirstStart(false))
|
||||
|
||||
// Get the new setting.
|
||||
require.False(t, bridge.GetFirstStart())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_Settings_FirstStartGUI(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// By default, first start is true.
|
||||
require.True(t, bridge.GetFirstStartGUI())
|
||||
|
||||
// Set first start to false.
|
||||
require.NoError(t, bridge.SetFirstStartGUI(false))
|
||||
|
||||
// Get the new setting.
|
||||
require.False(t, bridge.GetFirstStartGUI())
|
||||
})
|
||||
})
|
||||
}
|
||||
116
internal/bridge/smtp.go
Normal file
@ -0,0 +1,116 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/emersion/go-sasl"
|
||||
"github.com/emersion/go-smtp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (bridge *Bridge) serveSMTP() error {
|
||||
logrus.Info("Starting SMTP server")
|
||||
|
||||
smtpListener, err := newListener(bridge.vault.GetSMTPPort(), bridge.vault.GetSMTPSSL(), bridge.tlsConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create SMTP listener: %w", err)
|
||||
}
|
||||
|
||||
bridge.smtpListener = smtpListener
|
||||
|
||||
bridge.tasks.Once(func(context.Context) {
|
||||
if err := bridge.smtpServer.Serve(smtpListener); err != nil {
|
||||
logrus.WithError(err).Info("SMTP server stopped")
|
||||
}
|
||||
})
|
||||
|
||||
if err := bridge.vault.SetSMTPPort(getPort(smtpListener.Addr())); err != nil {
|
||||
return fmt.Errorf("failed to set IMAP port: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bridge *Bridge) restartSMTP() error {
|
||||
logrus.Info("Restarting SMTP server")
|
||||
|
||||
if err := bridge.closeSMTP(); err != nil {
|
||||
return fmt.Errorf("failed to close SMTP: %w", err)
|
||||
}
|
||||
|
||||
bridge.smtpServer = newSMTPServer(bridge, bridge.tlsConfig, bridge.logSMTP)
|
||||
|
||||
return bridge.serveSMTP()
|
||||
}
|
||||
|
||||
// We close the listener ourselves even though it's also closed by smtpServer.Close().
|
||||
// This is because smtpServer.Serve() is called in a separate goroutine and might be executed
|
||||
// after we've already closed the server. However, go-smtp has a bug; it blocks on the listener
|
||||
// even after the server has been closed. So we close the listener ourselves to unblock it.
|
||||
func (bridge *Bridge) closeSMTP() error {
|
||||
logrus.Info("Closing SMTP server")
|
||||
|
||||
if bridge.smtpListener != nil {
|
||||
if err := bridge.smtpListener.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close SMTP listener: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := bridge.smtpServer.Close(); err != nil {
|
||||
logrus.WithError(err).Debug("Failed to close SMTP server (expected -- we close the listener ourselves)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newSMTPServer(bridge *Bridge, tlsConfig *tls.Config, logSMTP bool) *smtp.Server {
|
||||
logrus.WithField("logSMTP", logSMTP).Info("Creating SMTP server")
|
||||
|
||||
smtpServer := smtp.NewServer(&smtpBackend{Bridge: bridge})
|
||||
|
||||
smtpServer.TLSConfig = tlsConfig
|
||||
smtpServer.Domain = constants.Host
|
||||
smtpServer.AllowInsecureAuth = true
|
||||
smtpServer.MaxLineLength = 1 << 16
|
||||
smtpServer.ErrorLog = logging.NewSMTPLogger()
|
||||
|
||||
// go-smtp suppors SASL PLAIN but not LOGIN. We need to add LOGIN support ourselves.
|
||||
smtpServer.EnableAuth(sasl.Login, func(conn *smtp.Conn) sasl.Server {
|
||||
return sasl.NewLoginServer(func(username, password string) error {
|
||||
return conn.Session().AuthPlain(username, password)
|
||||
})
|
||||
})
|
||||
|
||||
if logSMTP {
|
||||
log := logrus.WithField("protocol", "SMTP")
|
||||
log.Warning("================================================")
|
||||
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
|
||||
log.Warning("================================================")
|
||||
|
||||
smtpServer.Debug = logging.NewSMTPDebugLogger()
|
||||
}
|
||||
|
||||
return smtpServer
|
||||
}
|
||||
96
internal/bridge/smtp_backend.go
Normal file
@ -0,0 +1,96 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/emersion/go-smtp"
|
||||
)
|
||||
|
||||
type smtpBackend struct {
|
||||
*Bridge
|
||||
}
|
||||
|
||||
type smtpSession struct {
|
||||
*Bridge
|
||||
|
||||
userID string
|
||||
authID string
|
||||
|
||||
from string
|
||||
to []string
|
||||
}
|
||||
|
||||
func (be *smtpBackend) NewSession(*smtp.Conn) (smtp.Session, error) {
|
||||
return &smtpSession{Bridge: be.Bridge}, nil
|
||||
}
|
||||
|
||||
func (s *smtpSession) AuthPlain(username, password string) error {
|
||||
return safe.RLockRet(func() error {
|
||||
for _, user := range s.users {
|
||||
addrID, err := user.CheckAuth(username, []byte(password))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
s.userID = user.ID()
|
||||
s.authID = addrID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("invalid username or password")
|
||||
}, s.usersLock)
|
||||
}
|
||||
|
||||
func (s *smtpSession) Reset() {
|
||||
s.from = ""
|
||||
s.to = nil
|
||||
}
|
||||
|
||||
func (s *smtpSession) Logout() error {
|
||||
s.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *smtpSession) Mail(from string, opts *smtp.MailOptions) error {
|
||||
s.from = from
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *smtpSession) Rcpt(to string) error {
|
||||
if len(to) > 0 {
|
||||
s.to = append(s.to, to)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *smtpSession) Data(r io.Reader) error {
|
||||
return safe.RLockRet(func() error {
|
||||
user, ok := s.users[s.userID]
|
||||
if !ok {
|
||||
return ErrNoSuchUser
|
||||
}
|
||||
|
||||
return user.SendMail(s.authID, s.from, s.to, r)
|
||||
}, s.usersLock)
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
// Copyright (c) 2020 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/>.
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
)
|
||||
|
||||
type storeFactory struct {
|
||||
config StoreFactoryConfiger
|
||||
panicHandler users.PanicHandler
|
||||
clientManager users.ClientManager
|
||||
eventListener listener.Listener
|
||||
storeCache *store.Cache
|
||||
}
|
||||
|
||||
func newStoreFactory(
|
||||
config StoreFactoryConfiger,
|
||||
panicHandler users.PanicHandler,
|
||||
clientManager users.ClientManager,
|
||||
eventListener listener.Listener,
|
||||
) *storeFactory {
|
||||
return &storeFactory{
|
||||
config: config,
|
||||
panicHandler: panicHandler,
|
||||
clientManager: clientManager,
|
||||
eventListener: eventListener,
|
||||
storeCache: store.NewCache(config.GetIMAPCachePath()),
|
||||
}
|
||||
}
|
||||
|
||||
// New creates new store for given user.
|
||||
func (f *storeFactory) New(user store.BridgeUser) (*store.Store, error) {
|
||||
storePath := getUserStorePath(f.config.GetDBDir(), user.ID())
|
||||
return store.New(f.panicHandler, user, f.clientManager, f.eventListener, storePath, f.storeCache)
|
||||
}
|
||||
|
||||
// Remove removes all store files for given user.
|
||||
func (f *storeFactory) Remove(userID string) error {
|
||||
storePath := getUserStorePath(f.config.GetDBDir(), userID)
|
||||
return store.RemoveStore(f.storeCache, storePath, userID)
|
||||
}
|
||||
|
||||
// getUserStorePath returns the file path of the store database for the given userID.
|
||||
func getUserStorePath(storeDir string, userID string) (path string) {
|
||||
fileName := fmt.Sprintf("mailbox-%v.db", userID)
|
||||
return filepath.Join(storeDir, fileName)
|
||||
}
|
||||