Compare commits
842 Commits
folderCont
...
v6.0.9beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f48fe48f19 | ||
|
|
8f8470cf10 | ||
|
|
0a35d65b5d | ||
|
|
24b21d2ed1 | ||
|
|
a9838b37f1 | ||
|
|
7ee4acdf57 | ||
|
|
42cc039098 | ||
|
|
b49deb6d79 | ||
|
|
049d499aec | ||
|
|
b1755bb7b0 | ||
|
|
ba788a3539 | ||
|
|
42293195bc | ||
|
|
18b4d54f8a | ||
|
|
1b0756e49a | ||
|
|
84fcf80664 | ||
|
|
5154cc3e2c | ||
|
|
bf0f1a5092 | ||
|
|
0899f66232 | ||
|
|
231bc00a50 | ||
|
|
f22e5da347 | ||
|
|
a917b78c3c | ||
|
|
68000eb1cf | ||
|
|
87345f80ea | ||
|
|
b864fb4cc8 | ||
|
|
24911a36f7 | ||
|
|
9f01059ed9 | ||
|
|
e9e1b0638c | ||
|
|
267341fbeb | ||
|
|
0bccd06bc8 | ||
|
|
cbb58b0d2f | ||
|
|
df5b5e4cf8 | ||
|
|
3adb47094e | ||
|
|
299ff5a3b1 | ||
|
|
aca2807da6 | ||
|
|
441cdccc3e | ||
|
|
c59b22ab55 | ||
|
|
60e955bde4 | ||
|
|
3cb244dd23 | ||
|
|
78edcd9696 | ||
|
|
9320225daa | ||
|
|
9ea90ac82f | ||
|
|
e46f87fdac | ||
|
|
6fc04c2f91 | ||
|
|
55573645c6 | ||
|
|
de8c624a15 | ||
|
|
65b439da8c | ||
|
|
4e6a4e53cf | ||
|
|
870dc981aa | ||
|
|
979d2823b5 | ||
|
|
d63bba391d | ||
|
|
784931f835 | ||
|
|
962b410fcc | ||
|
|
9878f00118 | ||
|
|
828a939f96 | ||
|
|
9327abd2e4 | ||
|
|
07377be378 | ||
|
|
fd55d73ac5 | ||
|
|
1aedc60aa2 | ||
|
|
2107915073 | ||
|
|
abc2aad2a1 | ||
|
|
cb74d8ac0e | ||
|
|
5af5bd5498 | ||
|
|
564d28b57c | ||
|
|
af97529339 | ||
|
|
3844585f91 | ||
|
|
dfa57cdc01 | ||
|
|
c526ebb832 | ||
|
|
94d5f9e6e2 | ||
|
|
65c655afad | ||
|
|
befbf1c150 | ||
|
|
d9ceeed60d | ||
|
|
5e0ea9f3b5 | ||
|
|
b358547699 | ||
|
|
e9cc95a403 | ||
|
|
a84250c6d1 | ||
|
|
79e6b60199 | ||
|
|
01ce97f85f | ||
|
|
03694be08a | ||
|
|
5525294f9b | ||
|
|
e9cf3b4e8b | ||
|
|
82c375cda5 | ||
|
|
ed6588ff21 | ||
|
|
34a803d83e | ||
|
|
716613fda4 | ||
|
|
3ec5cbb347 | ||
|
|
f2629142b0 | ||
|
|
b2798afef5 | ||
|
|
5de19f38c4 | ||
|
|
77a4b1609b | ||
|
|
e001dfb3be | ||
|
|
e123b6db68 | ||
|
|
45489ecce4 | ||
|
|
7539309742 | ||
|
|
7217fb873c | ||
|
|
b963723c21 | ||
|
|
7a2dbc25cb | ||
|
|
16c5925155 | ||
|
|
5ecbd9ddd6 | ||
|
|
c3002b3053 | ||
|
|
e962c190c7 | ||
|
|
725360fa14 | ||
|
|
69f198d6fb | ||
|
|
7507995485 | ||
|
|
4a4f52e7a4 | ||
|
|
8d786ec456 | ||
|
|
9c4fe0bc3a | ||
|
|
25524e03c4 | ||
|
|
da1d4bf4fd | ||
|
|
1d5a664dc0 | ||
|
|
4a3e6dd528 | ||
|
|
c2e7c19c49 | ||
|
|
f3d335b7c1 | ||
|
|
6da05d0ca4 | ||
|
|
c82f158c16 | ||
|
|
f2b072bceb | ||
|
|
ee8ded88b3 | ||
|
|
3c65a503b2 | ||
|
|
8f45e8107a | ||
|
|
30098c34ad | ||
|
|
e895f06165 | ||
|
|
96430a99f9 | ||
|
|
eb276df08c | ||
|
|
d9ecfa3bd4 | ||
|
|
a5aa04ce19 | ||
|
|
1e692e56e1 | ||
|
|
663daf4bc7 | ||
|
|
8f0d89a986 | ||
|
|
6d6dfe2329 | ||
|
|
b067848509 | ||
|
|
ca33b2af72 | ||
|
|
f82aed0075 | ||
|
|
5e85790f5e | ||
|
|
ffe3bd1429 | ||
|
|
851d478691 | ||
|
|
74a994c561 | ||
|
|
534336162e | ||
|
|
274de5af86 | ||
|
|
2a34f530b2 | ||
|
|
d046527409 | ||
|
|
e7f9c21fb3 | ||
|
|
e1a77bdcc9 | ||
|
|
6b38c1de68 | ||
|
|
be07def0af | ||
|
|
66cccd4e4e | ||
|
|
2c192a1b76 | ||
|
|
ca29c8bab1 | ||
|
|
f00563c652 | ||
|
|
3368af82aa | ||
|
|
a66b2f56b9 | ||
|
|
8d5081f58f | ||
|
|
f99f48e26a | ||
|
|
6fb60e9d4a | ||
|
|
e8be18a8d8 | ||
|
|
3a27cc5221 | ||
|
|
a47aad8628 | ||
|
|
3e2e766f64 | ||
|
|
51185c1b53 | ||
|
|
6ef2acb82b | ||
|
|
304cf0b90c | ||
|
|
add4ce4319 | ||
|
|
68fa6e6620 | ||
|
|
e4b4c6ce6f | ||
|
|
82be3674c9 | ||
|
|
c3c87b012a | ||
|
|
2aabe31cb5 | ||
|
|
afa8006d7c | ||
|
|
c83c1749ce | ||
|
|
0477d56f5b | ||
|
|
b77298b76d | ||
|
|
d5d55412e1 | ||
|
|
a8ec0a7ccc | ||
|
|
3c3ebd5cf9 | ||
|
|
fca5af206f | ||
|
|
bfe76fa9e2 | ||
|
|
d9e9191248 | ||
|
|
bbc997aa5e | ||
|
|
114ce30153 | ||
|
|
3bec0dc4f1 | ||
|
|
b87591a537 | ||
|
|
46ade71b5a | ||
|
|
22cef86b91 | ||
|
|
cf46391f7c | ||
|
|
ff8cd422b5 | ||
|
|
e8167999dc | ||
|
|
e14e4f1dc4 | ||
|
|
28eabc15bf | ||
|
|
a609d5e031 | ||
|
|
da7641c232 | ||
|
|
2e264b3a02 | ||
|
|
9a44862979 | ||
|
|
e4fb6d536b | ||
|
|
46cc6f5747 | ||
|
|
b66d3632ea | ||
|
|
feca3d0ea9 | ||
|
|
6eb39f2194 | ||
|
|
26ded10052 | ||
|
|
267c0ff567 | ||
|
|
4b843dfcd1 | ||
|
|
508d84dcd8 | ||
|
|
58d04d1772 | ||
|
|
9caa354cfc | ||
|
|
0ba9a6b73d | ||
|
|
c491fa272e | ||
|
|
e23e459c41 | ||
|
|
78feb6514b | ||
|
|
1c634ab772 | ||
|
|
53e036e5c0 | ||
|
|
f56a9c08f8 | ||
|
|
659a3d4a37 | ||
|
|
3d26896431 | ||
|
|
f6c970e214 | ||
|
|
84222e30a7 | ||
|
|
81fc7cfb21 | ||
|
|
04817fb0f3 | ||
|
|
bca536eb3d | ||
|
|
64534546be | ||
|
|
00ec5fc193 | ||
|
|
8ef1542ad0 | ||
|
|
eeca726d28 | ||
|
|
e793a87954 | ||
|
|
6e0a218d11 | ||
|
|
b2fae8a8b7 | ||
|
|
193d237b56 | ||
|
|
4bc091965f | ||
|
|
3ea94a7930 | ||
|
|
129bfad204 | ||
|
|
4d7e1c568c | ||
|
|
f36e354a42 | ||
|
|
81f6e78c0c | ||
|
|
a543f46699 | ||
|
|
2895261102 | ||
|
|
2911906586 | ||
|
|
909913d581 | ||
|
|
edced60ed4 | ||
|
|
45d165405f | ||
|
|
e98627d6ab | ||
|
|
a4c422a6da | ||
|
|
6ec29711fc | ||
|
|
49f2955119 | ||
|
|
b62ba9808a | ||
|
|
f814b9a23c | ||
|
|
b3c8b574df | ||
|
|
79b953a920 | ||
|
|
d3445d9668 | ||
|
|
e5dcec75ba | ||
|
|
a908e281e7 | ||
|
|
2910486979 | ||
|
|
58b6b89ec4 | ||
|
|
8758b66fc4 | ||
|
|
8429dde4d9 | ||
|
|
37ef48d9f4 | ||
|
|
776d656ea9 | ||
|
|
d17335d7a8 | ||
|
|
84b239f123 | ||
|
|
ab3b713c23 | ||
|
|
1eedab7f6e | ||
|
|
9a5da34721 | ||
|
|
6f29e1c67b | ||
|
|
516f75fc6b | ||
|
|
9f405339d6 | ||
|
|
419e0354d5 | ||
|
|
313324a7b4 | ||
|
|
c18f2943d5 | ||
|
|
0c355cb1fb | ||
|
|
68434510bd | ||
|
|
1e6b4576c2 | ||
|
|
8c1eff8f52 | ||
|
|
37e29169e0 | ||
|
|
b91e3be5de | ||
|
|
113cd404c0 | ||
|
|
d2dbab21fb | ||
|
|
a152f3a50f | ||
|
|
c341a971f8 | ||
|
|
1d2dc9b685 | ||
|
|
4e1d57e815 | ||
|
|
76cdd85cd9 | ||
|
|
6f8a24c2cd | ||
|
|
835025e569 | ||
|
|
821ad6ad41 | ||
|
|
3fe45b1d4c | ||
|
|
86757cdb45 | ||
|
|
65ea07fc54 | ||
|
|
a3d93f0ba1 | ||
|
|
283a8b9624 | ||
|
|
dacacacc4f | ||
|
|
4f9a5c9d3c | ||
|
|
e5def25f40 | ||
|
|
2c92ef9e64 | ||
|
|
16ae62f246 | ||
|
|
53df9c93c4 | ||
|
|
14c95086a9 | ||
|
|
4696b06060 | ||
|
|
0a9487ec80 | ||
|
|
fcffccf018 | ||
|
|
72b90816fa | ||
|
|
6aeb5996b6 | ||
|
|
af45da94b3 | ||
|
|
f93ab1105f | ||
|
|
637aa56197 | ||
|
|
649233e54c | ||
|
|
8d00f1ca1f | ||
|
|
60edf98c15 | ||
|
|
1041617a4e | ||
|
|
a314508543 | ||
|
|
74241140d2 | ||
|
|
d3a15b6b07 | ||
|
|
1403a85950 | ||
|
|
130f2419f5 | ||
|
|
a53ec9ebeb | ||
|
|
33bdab1618 | ||
|
|
3405342ab6 | ||
|
|
980b11f096 | ||
|
|
22485fe743 | ||
|
|
a7bc716b95 | ||
|
|
9b36224bd4 | ||
|
|
9f1788f202 | ||
|
|
a30040411c | ||
|
|
c4d4065dc2 | ||
|
|
d4be12ab21 | ||
|
|
02de48e1ba | ||
|
|
97d6db966c | ||
|
|
cf2fc58c65 | ||
|
|
e1b1ce684d | ||
|
|
793f20d728 | ||
|
|
15ebb4b8ad | ||
|
|
80a294b334 | ||
|
|
6636c14c2a | ||
|
|
bf898f90bc | ||
|
|
de7a3861f3 | ||
|
|
d4951c68f3 | ||
|
|
85e90c33fc | ||
|
|
22632a227c | ||
|
|
fc8004d335 | ||
|
|
2149670328 | ||
|
|
2741eb1fe3 | ||
|
|
b09614b61b | ||
|
|
2257ae987f | ||
|
|
88949d7f94 | ||
|
|
e34d00a958 | ||
|
|
6b15731bfe | ||
|
|
be30728bc2 | ||
|
|
d34e1b524a | ||
|
|
3debb33346 | ||
|
|
79635ccb41 | ||
|
|
d8097b48ea | ||
|
|
1116455b3f | ||
|
|
09802ac217 | ||
|
|
984586c20a | ||
|
|
a591160c32 | ||
|
|
6dd3215eae | ||
|
|
e96c365054 | ||
|
|
2150cac956 | ||
|
|
096b4d8b1b | ||
|
|
47e3e73c8f | ||
|
|
519ab3a9c8 | ||
|
|
5df99d3e61 | ||
|
|
bd5eec7bb7 | ||
|
|
2086e80e4d | ||
|
|
f84168253b | ||
|
|
a390bd2e73 | ||
|
|
456f02b476 | ||
|
|
027de54558 | ||
|
|
7dd34b1212 | ||
|
|
24274acd6a | ||
|
|
5099caa55a | ||
|
|
17e5c03a02 | ||
|
|
a31b37e733 | ||
|
|
35e4784202 | ||
|
|
f464620a65 | ||
|
|
2d639b83ab | ||
|
|
406747ab30 | ||
|
|
b41c1d7655 | ||
|
|
b10b8a470b | ||
|
|
23ffdcc314 | ||
|
|
ffdbdf7496 | ||
|
|
df636371a7 | ||
|
|
ceff4dae61 | ||
|
|
540d4af8a7 | ||
|
|
bb995c53c5 | ||
|
|
eacb4b3f3e | ||
|
|
a70b99ff62 | ||
|
|
2c2fd5f3a0 | ||
|
|
288c40e2a6 | ||
|
|
d85d3a4493 | ||
|
|
a1c0c5e1a2 | ||
|
|
6ccc8173d3 | ||
|
|
2cb342c3b8 | ||
|
|
58db31e61e | ||
|
|
30cca2524f | ||
|
|
53ad7ebfea | ||
|
|
bff2c20fce | ||
|
|
e4104e63d9 | ||
|
|
bc5145e267 | ||
|
|
544495563f | ||
|
|
112a277998 | ||
|
|
46657ad8a6 | ||
|
|
14b9eba428 | ||
|
|
6a31fecca3 | ||
|
|
3ccbc25dba | ||
|
|
c3c255b0ca | ||
|
|
2e42b0555a | ||
|
|
c1c6ce22e8 | ||
|
|
a3bc3bbf2d | ||
|
|
37e19534f1 | ||
|
|
d290f44fc7 | ||
|
|
4966d632ae | ||
|
|
a5871c1811 | ||
|
|
0f40acb9a8 | ||
|
|
cf2af3ce4d | ||
|
|
d54f314c93 | ||
|
|
ff078db8cb | ||
|
|
acc35849af | ||
|
|
c9c91c7e15 | ||
|
|
ce5f8d72a4 | ||
|
|
b4c10eea60 | ||
|
|
c358373dfa | ||
|
|
95ddadd146 | ||
|
|
91392c5a87 | ||
|
|
26b70bce28 | ||
|
|
7f81b14eff | ||
|
|
87d385303f | ||
|
|
eaf96335ad | ||
|
|
d7163c9b5a | ||
|
|
98ff74a70e | ||
|
|
f1b948d4dc | ||
|
|
9b6b02af6d | ||
|
|
6b45835ecf | ||
|
|
c02c815603 | ||
|
|
5bf9aab464 | ||
|
|
722b81627c | ||
|
|
7e2659f017 | ||
|
|
52b3187962 | ||
|
|
b3c5aaa1ca | ||
|
|
ea62d23cb3 | ||
|
|
289f64b191 | ||
|
|
5878d27068 | ||
|
|
071e371803 | ||
|
|
ccf2bf74ea | ||
|
|
9e439305fd | ||
|
|
dadd67e7bd | ||
|
|
762b0d9510 | ||
|
|
dc0a7428bd | ||
|
|
7534fae9bc | ||
|
|
68a9ceacbb | ||
|
|
8a3a5a2ff1 | ||
|
|
8995c81103 | ||
|
|
095f9114dd | ||
|
|
470a0ad611 | ||
|
|
9661001af1 | ||
|
|
17f869b2fb | ||
|
|
162ba722f6 | ||
|
|
4756d18a14 | ||
|
|
9cff832282 | ||
|
|
7ced22ccc6 | ||
|
|
6455722f29 | ||
|
|
8b3d99308c | ||
|
|
f87319f834 | ||
|
|
4605c2cc2d | ||
|
|
49e567a5cc | ||
|
|
7580518bd8 | ||
|
|
d93b5af265 | ||
|
|
9a0c78b831 | ||
|
|
61a37331ce | ||
|
|
d265d290c3 | ||
|
|
aff7594ef3 | ||
|
|
5e586995a8 | ||
|
|
1d0e45e179 | ||
|
|
cab94eb719 | ||
|
|
c1cc4df26e | ||
|
|
b4f154f9b4 | ||
|
|
ac3f1d6f61 | ||
|
|
d27f2929d2 | ||
|
|
6aee04bba4 | ||
|
|
a88b253c78 | ||
|
|
751c1b0418 | ||
|
|
a0feffb2a7 | ||
|
|
34d17e6168 | ||
|
|
c30377392b | ||
|
|
0d2482a1a9 | ||
|
|
149814d95b | ||
|
|
16e24dc4c5 | ||
|
|
dc11b87a1d | ||
|
|
c8d34eaf9c | ||
|
|
9183ade655 | ||
|
|
678afc4906 | ||
|
|
6d3b5b24fd | ||
|
|
4286eeb531 | ||
|
|
0187537a2e | ||
|
|
928947f1c3 | ||
|
|
fb1ebf13c6 | ||
|
|
ed7e9be7cd | ||
|
|
e903c8b57b | ||
|
|
210832ddf1 | ||
|
|
96ebefc597 | ||
|
|
5348e2eb54 | ||
|
|
f6e6f465f2 | ||
|
|
ce87f933a4 | ||
|
|
a04af7854d | ||
|
|
75c7fd2886 | ||
|
|
92b4d52079 | ||
|
|
91b4045791 | ||
|
|
0f87a0248f | ||
|
|
3c9421381b | ||
|
|
2d946d2766 | ||
|
|
a422f19fac | ||
|
|
ebb1a70abc | ||
|
|
b9d013e3ce | ||
|
|
b044ec0420 | ||
|
|
3520529430 | ||
|
|
864f0342af | ||
|
|
5a2e99c975 | ||
|
|
d07f459409 | ||
|
|
3d5d6b5ad1 | ||
|
|
80dcec773a | ||
|
|
43738dacfb | ||
|
|
78b10de7d6 | ||
|
|
37db8a13d4 | ||
|
|
eb8b2210cd | ||
|
|
118033cac6 | ||
|
|
b0ba69ff31 | ||
|
|
c1241b1691 | ||
|
|
ad813da7b1 | ||
|
|
b4f04c18db | ||
|
|
23985428ea | ||
|
|
018fed6014 | ||
|
|
3be50224ff | ||
|
|
0213928735 | ||
|
|
1cf599d494 | ||
|
|
8a4dbdedcf | ||
|
|
35b316f93f | ||
|
|
7e451a24bc | ||
|
|
f8620704d4 | ||
|
|
c6687e159b | ||
|
|
8227bc41cd | ||
|
|
6f5caaa87a | ||
|
|
e5dac9fa24 | ||
|
|
48bb8aeb01 | ||
|
|
fd32501fe0 | ||
|
|
0a475bdab3 | ||
|
|
3d6ab2b53b | ||
|
|
5e315495c2 | ||
|
|
b353637e45 | ||
|
|
fb6290c87a | ||
|
|
76cb5a50c0 | ||
|
|
5c40ef090a | ||
|
|
9c9b161f51 | ||
|
|
9b257ad862 | ||
|
|
5edc4c95bc | ||
|
|
f9bd4c5d24 | ||
|
|
88a5f7d994 | ||
|
|
04b4c7c042 | ||
|
|
87d06bae85 | ||
|
|
c096c6edc3 | ||
|
|
713c5c4275 | ||
|
|
e48c7675b9 | ||
|
|
f824986742 | ||
|
|
5e788ae1fa | ||
|
|
78b390da31 | ||
|
|
b3257a315d | ||
|
|
fc1763dc6b | ||
|
|
7b948b0580 | ||
|
|
379ddd88d6 | ||
|
|
23c015ba52 | ||
|
|
0f97d9555c | ||
|
|
0992a77cfb | ||
|
|
4b1f93b61c | ||
|
|
916122b166 | ||
|
|
a3b351738a | ||
|
|
37f8607a88 | ||
|
|
a4895e4df1 | ||
|
|
a9a92892be | ||
|
|
d8770d1284 | ||
|
|
e3d8758a32 | ||
|
|
5a5496f4e7 | ||
|
|
b33cfce2fa | ||
|
|
025ab0dabb | ||
|
|
63b078b550 | ||
|
|
fa1288c6b3 | ||
|
|
9f9466d001 | ||
|
|
249df3864b | ||
|
|
615f0c4a1c | ||
|
|
a096f19bb2 | ||
|
|
a967acee4c | ||
|
|
2801edc451 | ||
|
|
acebc3f41e | ||
|
|
9404389d83 | ||
|
|
60b75b25f8 | ||
|
|
608fe55889 | ||
|
|
37d2c0a4be | ||
|
|
3ccbc984f4 | ||
|
|
8bd3ce2fe2 | ||
|
|
ebcbf85396 | ||
|
|
44a6d2a98c | ||
|
|
8ee368ddad | ||
|
|
34069e00c4 | ||
|
|
88542819f0 | ||
|
|
8a66c30895 | ||
|
|
e6579a3c0a | ||
|
|
9016093b4c | ||
|
|
301e4988a1 | ||
|
|
e6391d84a1 | ||
|
|
4c305ef459 | ||
|
|
72e308788f | ||
|
|
f1dc6e1441 | ||
|
|
33801c311e | ||
|
|
0cd68cfedc | ||
|
|
e00f483238 | ||
|
|
5cc8df3723 | ||
|
|
08bf128255 | ||
|
|
3979508dae | ||
|
|
a5a2fe9180 | ||
|
|
29d63893ff | ||
|
|
39354eaaf1 | ||
|
|
2e8418b362 | ||
|
|
1a93891222 | ||
|
|
099ccfa254 | ||
|
|
d41b2c5401 | ||
|
|
5f7bc2cf4d | ||
|
|
6d330e8a3f | ||
|
|
72b680ca2d | ||
|
|
5e1c3732e5 | ||
|
|
7251c65996 | ||
|
|
fea0ba84c5 | ||
|
|
cdfc6ff5d5 | ||
|
|
50721da77b | ||
|
|
14cbec6d6b | ||
|
|
ea334cfa18 | ||
|
|
c10af30381 | ||
|
|
4351609df9 | ||
|
|
c430fe6612 | ||
|
|
3501007074 | ||
|
|
603828366f | ||
|
|
e46df97c3a | ||
|
|
180bb9fa9f | ||
|
|
81f1c04b85 | ||
|
|
b722ce6cc3 | ||
|
|
65fa896bc2 | ||
|
|
464b31157e | ||
|
|
a9f7764879 | ||
|
|
047ae7fb23 | ||
|
|
98db90ba70 | ||
|
|
0b11762b1c | ||
|
|
127ab3cb47 | ||
|
|
09cc0c5cc9 | ||
|
|
c4c5e34110 | ||
|
|
89d60a2680 | ||
|
|
369ec65d12 | ||
|
|
40c2f47cd9 | ||
|
|
bc22799160 | ||
|
|
4c1510b729 | ||
|
|
d44b98d3dd | ||
|
|
a4e842f19a | ||
|
|
aec0f06501 | ||
|
|
f9e4d0a5e2 | ||
|
|
3093c63024 | ||
|
|
b0edbd8ebd | ||
|
|
5dd20db05f | ||
|
|
0c140d5f6e | ||
|
|
49969825ad | ||
|
|
3b21499b34 | ||
|
|
3fde29ead4 | ||
|
|
afe35ca12d | ||
|
|
a83e37c799 | ||
|
|
3c7541e97c | ||
|
|
3ec3ebb3db | ||
|
|
3e71827bea | ||
|
|
6adac6a673 | ||
|
|
88dc71381f | ||
|
|
c6ca9be406 | ||
|
|
7dddf59942 | ||
|
|
8ef32821be | ||
|
|
1e9f0409c3 | ||
|
|
bcec9cea78 | ||
|
|
87cfbb3a2b | ||
|
|
3b1396b538 | ||
|
|
ce1a1996f7 | ||
|
|
5f2e8f7723 | ||
|
|
54cd174f61 | ||
|
|
28be8496a9 | ||
|
|
3ae7bfc298 | ||
|
|
1cbe1896e7 | ||
|
|
ebe3872c64 | ||
|
|
f1ea0bcafc | ||
|
|
cf8fc2294e | ||
|
|
1636c6dbd8 | ||
|
|
59ff8388ea | ||
|
|
1c58d8b226 | ||
|
|
0f58bd02c1 | ||
|
|
4b4901519d | ||
|
|
a711d4e39a | ||
|
|
52dc90d5fd | ||
|
|
9a1cbabdd6 | ||
|
|
596ab7552e | ||
|
|
ea23a09c05 | ||
|
|
394bd17251 | ||
|
|
627612c9e0 | ||
|
|
c6fa2dd8e2 | ||
|
|
8d38228c98 | ||
|
|
bbba8fd09d | ||
|
|
5e928fc988 | ||
|
|
7e36763631 | ||
|
|
542cf79595 | ||
|
|
2ea3765359 | ||
|
|
b7aaebf94f | ||
|
|
c9cb258616 | ||
|
|
697ad1ca9e | ||
|
|
6a838718df | ||
|
|
6a21922854 | ||
|
|
c81a05cbb1 | ||
|
|
956a4419d8 | ||
|
|
f356ef90d7 | ||
|
|
a8fd55d90d | ||
|
|
208037b56a | ||
|
|
62887c67ab | ||
|
|
9960596f4e | ||
|
|
b5ef21b22d | ||
|
|
44fb817a4e | ||
|
|
2c54dcda89 | ||
|
|
0bb7202ff7 | ||
|
|
75eaea1dff | ||
|
|
93b6c83814 | ||
|
|
d9df271113 | ||
|
|
13461698e4 | ||
|
|
eaa5dd0282 | ||
|
|
672cbc5378 | ||
|
|
2f568c9766 | ||
|
|
c4c972fd2f | ||
|
|
6de259e0ac | ||
|
|
1f93b4e842 | ||
|
|
4c2d2f4dc9 | ||
|
|
148c16bf70 | ||
|
|
77af1a7127 | ||
|
|
2f0ec8256f | ||
|
|
913df0c5ed | ||
|
|
c88109a496 | ||
|
|
8f5dfd0920 | ||
|
|
ae58a385b7 | ||
|
|
3160b23291 | ||
|
|
73a0b690e9 | ||
|
|
bdb4890d04 | ||
|
|
9091dd7833 | ||
|
|
a8a35f4d07 | ||
|
|
0d24b22485 | ||
|
|
e8a696cff9 | ||
|
|
811ef19ada | ||
|
|
db696a2dda | ||
|
|
3d81009347 | ||
|
|
0b665700b5 | ||
|
|
1e407482be | ||
|
|
d343c66f85 | ||
|
|
0a0d89296b | ||
|
|
fede36d515 | ||
|
|
527a16852e | ||
|
|
aab7c187f2 | ||
|
|
dd7b074dfa | ||
|
|
71e93c2b79 | ||
|
|
ca161e2a96 | ||
|
|
ab8691d84b | ||
|
|
a48cf28e17 | ||
|
|
cd7c974fb6 | ||
|
|
3b5d032dd5 | ||
|
|
f8088ecd29 | ||
|
|
e83ff69465 | ||
|
|
66af605697 | ||
|
|
71dc81d420 | ||
|
|
02c9bb76ff | ||
|
|
7b388eb4a8 | ||
|
|
c770089b50 | ||
|
|
afaac05f61 | ||
|
|
aba8199a82 | ||
|
|
0134b1ebfa | ||
|
|
339e073399 | ||
|
|
4feafda822 | ||
|
|
5ac339b866 | ||
|
|
95c90ddbc4 | ||
|
|
2417c8a49e | ||
|
|
26d0adf2fe | ||
|
|
734fdbddea | ||
|
|
33144941a9 | ||
|
|
f309920bef | ||
|
|
e78a309111 | ||
|
|
46a4ad4f3c | ||
|
|
3842c0d766 | ||
|
|
8369c1f05b | ||
|
|
cadf1c232e | ||
|
|
713651738c | ||
|
|
1475245465 | ||
|
|
281bbf4bd4 | ||
|
|
a2bbbb1448 | ||
|
|
87a28957aa | ||
|
|
01de6c565a | ||
|
|
f3e341edac | ||
|
|
c5d6af3c5a | ||
|
|
cc1577054d | ||
|
|
3e6b18b2d0 | ||
|
|
7eb505e6dd | ||
|
|
91ca1cf985 | ||
|
|
f8a4bcdd50 | ||
|
|
418d401bf9 | ||
|
|
d0afd774b7 | ||
|
|
0af65cae37 | ||
|
|
4d50fe5f12 | ||
|
|
f24aca61a9 | ||
|
|
d3e2226b38 | ||
|
|
73fa6259be | ||
|
|
dffc2a8b1c | ||
|
|
fd634fdec8 | ||
|
|
328ebaefde | ||
|
|
4de8c4e5db | ||
|
|
8dc3cb2ffb | ||
|
|
c67ece0f6b | ||
|
|
6640e120b7 | ||
|
|
6f7b394d99 | ||
|
|
91f47645b0 | ||
|
|
18903cc8f7 | ||
|
|
7e467484a8 | ||
|
|
21bf6abd41 | ||
|
|
401728b2a2 | ||
|
|
bfd0dc3314 | ||
|
|
ad92b12559 | ||
|
|
4391992b8c | ||
|
|
728e6d735a | ||
|
|
66df8c3bb3 | ||
|
|
ca08d21da0 | ||
|
|
7d0f458f11 | ||
|
|
f15aeb3bbc | ||
|
|
9333244925 | ||
|
|
50f5ab8515 | ||
|
|
4df0d2e7e8 | ||
|
|
f2e17a0b68 | ||
|
|
e372971f2b | ||
|
|
3bf30377b8 | ||
|
|
67b67b6d66 | ||
|
|
b8390f15d4 | ||
|
|
5ac3f9bfa5 | ||
|
|
02fe013d2f | ||
|
|
5e5e4fe3a4 | ||
|
|
d49e4e53fd | ||
|
|
2d11290121 | ||
|
|
1a197292da | ||
|
|
3b59979746 | ||
|
|
259d619af3 |
@@ -26,7 +26,7 @@ RewriteRule ^.well-known/carddav /remote.php/carddav/ [R]
|
||||
RewriteRule ^.well-known/caldav /remote.php/caldav/ [R]
|
||||
RewriteRule ^apps/calendar/caldav.php remote.php/caldav/ [QSA,L]
|
||||
RewriteRule ^apps/contacts/carddav.php remote.php/carddav/ [QSA,L]
|
||||
RewriteRule ^apps/([^/]*)/(.*\.(css|php))$ index.php?app=$1&getfile=$2 [QSA,L]
|
||||
RewriteRule ^apps/([^/]*)/(.*\.(php))$ index.php?app=$1&getfile=$2 [QSA,L]
|
||||
RewriteRule ^remote/(.*) remote.php [QSA,L]
|
||||
</IfModule>
|
||||
<IfModule mod_mime.c>
|
||||
@@ -38,3 +38,6 @@ DirectoryIndex index.php index.html
|
||||
</IfModule>
|
||||
AddDefaultCharset utf-8
|
||||
Options -Indexes
|
||||
<IfModule pagespeed_module>
|
||||
ModPagespeed Off
|
||||
</IfModule>
|
||||
|
||||
2
3rdparty
2
3rdparty
Submodule 3rdparty updated: 42efd96628...1cee06106a
@@ -17,7 +17,9 @@ $success = true;
|
||||
|
||||
//Now delete
|
||||
foreach ($files as $file) {
|
||||
if (($dir === '' && $file === 'Shared') || !\OC\Files\Filesystem::unlink($dir . '/' . $file)) {
|
||||
if (($dir === '' && $file === 'Shared') ||
|
||||
(\OC\Files\Filesystem::file_exists($dir . '/' . $file) &&
|
||||
!\OC\Files\Filesystem::unlink($dir . '/' . $file))) {
|
||||
$filesWithError .= $file . "\n";
|
||||
$success = false;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ $RUNTIME_APPTYPES=array('filesystem');
|
||||
OCP\JSON::checkLoggedIn();
|
||||
|
||||
// Load the files
|
||||
$dir = isset( $_GET['dir'] ) ? $_GET['dir'] : '';
|
||||
$dir = isset($_GET['dir']) ? (string)$_GET['dir'] : '';
|
||||
$dir = \OC\Files\Filesystem::normalizePath($dir);
|
||||
if (!\OC\Files\Filesystem::is_dir($dir . '/')) {
|
||||
header("HTTP/1.0 404 Not Found");
|
||||
|
||||
@@ -18,7 +18,7 @@ if(\OC\Files\Filesystem::file_exists($target . '/' . $file)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($dir != '' || $file != 'Shared') {
|
||||
if ($target != '' || strtolower($file) != 'shared') {
|
||||
$targetFile = \OC\Files\Filesystem::normalizePath($target . '/' . $file);
|
||||
$sourceFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $file);
|
||||
if(\OC\Files\Filesystem::rename($sourceFile, $targetFile)) {
|
||||
|
||||
@@ -77,14 +77,53 @@ if (\OC\Files\Filesystem::file_exists($target)) {
|
||||
}
|
||||
|
||||
if($source) {
|
||||
if(substr($source, 0, 8)!='https://' and substr($source, 0, 7)!='http://') {
|
||||
OCP\JSON::error(array('data' => array( 'message' => $l10n->t('Not a valid source') )));
|
||||
$httpHelper = \OC::$server->getHTTPHelper();
|
||||
if(!$httpHelper->isHTTPURL($source)) {
|
||||
OCP\JSON::error(array('data' => array('message' => $l10n->t('Not a valid source'))));
|
||||
exit();
|
||||
}
|
||||
|
||||
$ctx = stream_context_create(null, array('notification' =>'progress'));
|
||||
$sourceStream=fopen($source, 'rb', false, $ctx);
|
||||
$result=\OC\Files\Filesystem::file_put_contents($target, $sourceStream);
|
||||
if (!ini_get('allow_url_fopen')) {
|
||||
$eventSource->send('error', $l10n->t('Server is not allowed to open URLs, please check the server configuration'));
|
||||
$eventSource->close();
|
||||
exit();
|
||||
}
|
||||
|
||||
$source = $httpHelper->getFinalLocationOfURL($source);
|
||||
|
||||
$ctx = stream_context_create(\OC::$server->getHTTPHelper()->getDefaultContextArray(), array('notification' =>'progress'));
|
||||
|
||||
$sourceStream=@fopen($source, 'rb', false, $ctx);
|
||||
$result = 0;
|
||||
if (is_resource($sourceStream)) {
|
||||
$meta = stream_get_meta_data($sourceStream);
|
||||
if (isset($meta['wrapper_data']) && is_array($meta['wrapper_data'])) {
|
||||
//check stream size
|
||||
$storageStats = \OCA\Files\Helper::buildFileStorageStatistics($dir);
|
||||
$freeSpace = $storageStats['freeSpace'];
|
||||
|
||||
foreach($meta['wrapper_data'] as $header) {
|
||||
if (strpos($header, ':') === false){
|
||||
continue;
|
||||
}
|
||||
list($name, $value) = explode(':', $header);
|
||||
if ('content-length' === strtolower(trim($name))) {
|
||||
$length = (int) trim($value);
|
||||
|
||||
if ($length > $freeSpace) {
|
||||
$delta = $length - $freeSpace;
|
||||
$humanDelta = OCP\Util::humanFileSize($delta);
|
||||
|
||||
$eventSource->send('error', (string)$l10n->t('The file exceeds your quota by %s', array($humanDelta)));
|
||||
$eventSource->close();
|
||||
fclose($sourceStream);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$result=\OC\Files\Filesystem::file_put_contents($target, $sourceStream);
|
||||
}
|
||||
if($result) {
|
||||
$meta = \OC\Files\Filesystem::getFileInfo($target);
|
||||
$mime=$meta['mimetype'];
|
||||
@@ -93,6 +132,9 @@ if($source) {
|
||||
} else {
|
||||
$eventSource->send('error', $l10n->t('Error while downloading %s to %s', array($source, $target)));
|
||||
}
|
||||
if (is_resource($sourceStream)) {
|
||||
fclose($sourceStream);
|
||||
}
|
||||
$eventSource->close();
|
||||
exit();
|
||||
} else {
|
||||
|
||||
@@ -19,8 +19,12 @@ if (empty($_POST['dirToken'])) {
|
||||
die();
|
||||
}
|
||||
} else {
|
||||
|
||||
\OC_User::setIncognitoMode(true);
|
||||
|
||||
// return only read permissions for public upload
|
||||
$allowedPermissions = OCP\PERMISSION_READ;
|
||||
$public_directory = !empty($_POST['subdir']) ? $_POST['subdir'] : '/';
|
||||
|
||||
$linkItem = OCP\Share::getShareByToken($_POST['dirToken']);
|
||||
if ($linkItem === false) {
|
||||
@@ -34,16 +38,21 @@ if (empty($_POST['dirToken'])) {
|
||||
// resolve reshares
|
||||
$rootLinkItem = OCP\Share::resolveReShare($linkItem);
|
||||
|
||||
OCP\JSON::checkUserExists($rootLinkItem['uid_owner']);
|
||||
// Setup FS with owner
|
||||
OC_Util::tearDownFS();
|
||||
OC_Util::setupFS($rootLinkItem['uid_owner']);
|
||||
|
||||
// The token defines the target directory (security reasons)
|
||||
$path = \OC\Files\Filesystem::getPath($linkItem['file_source']);
|
||||
if($path === null) {
|
||||
OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Unable to set upload directory.')))));
|
||||
die();
|
||||
}
|
||||
$dir = sprintf(
|
||||
"/%s/%s",
|
||||
$path,
|
||||
isset($_POST['subdir']) ? $_POST['subdir'] : ''
|
||||
$public_directory
|
||||
);
|
||||
|
||||
if (!$dir || empty($dir) || $dir === false) {
|
||||
@@ -77,7 +86,9 @@ foreach ($_FILES['files']['error'] as $error) {
|
||||
UPLOAD_ERR_NO_TMP_DIR => $l->t('Missing a temporary folder'),
|
||||
UPLOAD_ERR_CANT_WRITE => $l->t('Failed to write to disk'),
|
||||
);
|
||||
OCP\JSON::error(array('data' => array_merge(array('message' => $errors[$error]), $storageStats)));
|
||||
$errorMessage = $errors[$error];
|
||||
\OCP\Util::writeLog('files', "Upload error: $error - $errorMessage", \OCP\Util::ERROR);
|
||||
OCP\JSON::error(array('data' => array_merge(array('message' => $errorMessage), $storageStats)));
|
||||
exit();
|
||||
}
|
||||
}
|
||||
@@ -110,7 +121,14 @@ if (strpos($dir, '..') === false) {
|
||||
} else {
|
||||
$target = \OC\Files\Filesystem::normalizePath(stripslashes($dir).'/'.$files['name'][$i]);
|
||||
}
|
||||
|
||||
|
||||
$directory = \OC\Files\Filesystem::normalizePath(stripslashes($dir));
|
||||
if (isset($public_directory)) {
|
||||
// If we are uploading from the public app,
|
||||
// we want to send the relative path in the ajax request.
|
||||
$directory = $public_directory;
|
||||
}
|
||||
|
||||
if ( ! \OC\Files\Filesystem::file_exists($target)
|
||||
|| (isset($_POST['resolution']) && $_POST['resolution']==='replace')
|
||||
) {
|
||||
@@ -136,7 +154,8 @@ if (strpos($dir, '..') === false) {
|
||||
'originalname' => $files['tmp_name'][$i],
|
||||
'uploadMaxFilesize' => $maxUploadFileSize,
|
||||
'maxHumanFilesize' => $maxHumanFileSize,
|
||||
'permissions' => $meta['permissions'] & $allowedPermissions
|
||||
'permissions' => $meta['permissions'] & $allowedPermissions,
|
||||
'directory' => $directory,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -146,7 +165,7 @@ if (strpos($dir, '..') === false) {
|
||||
} catch(Exception $ex) {
|
||||
$error = $ex->getMessage();
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
// file already exists
|
||||
$meta = \OC\Files\Filesystem::getFileInfo($target);
|
||||
@@ -163,7 +182,8 @@ if (strpos($dir, '..') === false) {
|
||||
'originalname' => $files['tmp_name'][$i],
|
||||
'uploadMaxFilesize' => $maxUploadFileSize,
|
||||
'maxHumanFilesize' => $maxHumanFileSize,
|
||||
'permissions' => $meta['permissions'] & $allowedPermissions
|
||||
'permissions' => $meta['permissions'] & $allowedPermissions,
|
||||
'directory' => $directory,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ $server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend, $defaults->getName())
|
||||
$server->addPlugin(new Sabre_DAV_Locks_Plugin($lockBackend));
|
||||
$server->addPlugin(new Sabre_DAV_Browser_Plugin(false)); // Show something in the Browser, but no upload
|
||||
$server->addPlugin(new OC_Connector_Sabre_FilesPlugin());
|
||||
$server->addPlugin(new OC_Connector_Sabre_AbortedUploadDetectionPlugin());
|
||||
$server->addPlugin(new OC_Connector_Sabre_QuotaPlugin());
|
||||
$server->addPlugin(new OC_Connector_Sabre_MaintenancePlugin());
|
||||
$server->addPlugin(new OC_Connector_Sabre_ExceptionLoggerPlugin('webdav'));
|
||||
|
||||
// And off we go!
|
||||
$server->exec();
|
||||
|
||||
@@ -3,17 +3,14 @@
|
||||
// fix webdav properties,add namespace in front of the property, update for OC4.5
|
||||
$installedVersion=OCP\Config::getAppValue('files', 'installed_version');
|
||||
if (version_compare($installedVersion, '1.1.6', '<')) {
|
||||
$query = OC_DB::prepare( 'SELECT `propertyname`, `propertypath`, `userid` FROM `*PREFIX*properties`' );
|
||||
$result = $query->execute();
|
||||
$updateQuery = OC_DB::prepare('UPDATE `*PREFIX*properties`'
|
||||
.' SET `propertyname` = ?'
|
||||
.' WHERE `userid` = ?'
|
||||
.' AND `propertypath` = ?');
|
||||
while( $row = $result->fetchRow()) {
|
||||
if ( $row['propertyname'][0] != '{' ) {
|
||||
$updateQuery->execute(array('{DAV:}' + $row['propertyname'], $row['userid'], $row['propertypath']));
|
||||
}
|
||||
}
|
||||
$concat = OC_DB::getConnection()->getDatabasePlatform()->
|
||||
getConcatExpression( '\'{DAV:}\'', '`propertyname`' );
|
||||
$query = OC_DB::prepare('
|
||||
UPDATE `*PREFIX*properties`
|
||||
SET `propertyname` = ' . $concat . '
|
||||
WHERE `propertyname` NOT LIKE \'{%\'
|
||||
');
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
//update from OC 3
|
||||
|
||||
@@ -57,6 +57,7 @@ class Scan extends Command {
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
\OC_App::loadApps('authentication');
|
||||
if ($input->getOption('all')) {
|
||||
$users = $this->userManager->search('');
|
||||
} else {
|
||||
@@ -67,7 +68,11 @@ class Scan extends Command {
|
||||
if (is_object($user)) {
|
||||
$user = $user->getUID();
|
||||
}
|
||||
$this->scanFiles($user, $output);
|
||||
if ($this->userManager->userExists($user)) {
|
||||
$this->scanFiles($user, $output);
|
||||
} else {
|
||||
$output->writeln("<error>Unknown user $user</error>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,9 +65,14 @@
|
||||
top: 44px;
|
||||
width: 100%;
|
||||
}
|
||||
#filestable, #controls {
|
||||
min-width: 680px;
|
||||
/* make sure there's enough room for the file actions */
|
||||
#body-user #filestable {
|
||||
min-width: 750px;
|
||||
}
|
||||
#body-user #controls {
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
#filestable tbody tr { background-color:#fff; height:2.5em; }
|
||||
#filestable tbody tr:hover, tbody tr:active {
|
||||
background-color: rgb(240,240,240);
|
||||
@@ -98,7 +103,7 @@ table td {
|
||||
}
|
||||
table th#headerName {
|
||||
position: relative;
|
||||
width: 100em; /* not really sure why this works better than 100% … table styling */
|
||||
width: 9999px; /* not really sure why this works better than 100% … table styling */
|
||||
padding: 0;
|
||||
}
|
||||
#headerName-container {
|
||||
@@ -140,7 +145,7 @@ table.multiselect thead th {
|
||||
}
|
||||
table.multiselect #headerName {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
width: 9999px; /* when we use 100%, the styling breaks on mobile … table styling */
|
||||
}
|
||||
table td.selection, table th.selection, table td.fileaction { width:2em; text-align:center; }
|
||||
table td.filename a.name {
|
||||
@@ -237,7 +242,7 @@ table td.filename form { font-size:.85em; margin-left:3em; margin-right:3em; }
|
||||
|
||||
#fileList tr td.filename a.name label {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
width: 80%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
margin: -5px -3px;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
background-image: url('%webroot%/core/img/actions/upload.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
opacity: .65;
|
||||
}
|
||||
.file_upload_target { display:none; }
|
||||
@@ -119,11 +116,6 @@
|
||||
.oc-dialog .fileexists .conflict input[type='checkbox'] {
|
||||
float: left;
|
||||
}
|
||||
.oc-dialog .fileexists .toggle {
|
||||
background-image: url('%webroot%/core/img/actions/triangle-e.png');
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.oc-dialog .fileexists #allfileslabel {
|
||||
float:right;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
// Check if we are a user
|
||||
OCP\User::checkLoggedIn();
|
||||
|
||||
// don't block php session during download
|
||||
session_write_close();
|
||||
|
||||
$filename = $_GET["file"];
|
||||
|
||||
if(!\OC\Files\Filesystem::file_exists($filename)) {
|
||||
@@ -37,12 +40,7 @@ if(!\OC\Files\Filesystem::file_exists($filename)) {
|
||||
$ftype=\OC\Files\Filesystem::getMimeType( $filename );
|
||||
|
||||
header('Content-Type:'.$ftype);
|
||||
if ( preg_match( "/MSIE/", $_SERVER["HTTP_USER_AGENT"] ) ) {
|
||||
header( 'Content-Disposition: attachment; filename="' . rawurlencode( basename($filename) ) . '"' );
|
||||
} else {
|
||||
header( 'Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode( basename($filename) )
|
||||
. '; filename="' . rawurlencode( basename($filename) ) . '"' );
|
||||
}
|
||||
OCP\Response::setContentDispositionHeader(basename($filename), 'attachment');
|
||||
OCP\Response::disableCaching();
|
||||
header('Content-Length: '.\OC\Files\Filesystem::filesize($filename));
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ OC.Upload = {
|
||||
*/
|
||||
isProcessing:function() {
|
||||
var count = 0;
|
||||
|
||||
|
||||
jQuery.each(this._uploads,function(i, data) {
|
||||
if (data.state() === 'pending') {
|
||||
count++;
|
||||
@@ -170,7 +170,7 @@ OC.Upload = {
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
if ( $('#file_upload_start').exists() ) {
|
||||
if ( $('#file_upload_start').exists()&& $('#file_upload_start').is(':visible') ) {
|
||||
|
||||
var file_upload_param = {
|
||||
dropZone: $('#content'), // restrict dropZone to content div
|
||||
@@ -196,13 +196,13 @@ $(document).ready(function() {
|
||||
add: function(e, data) {
|
||||
OC.Upload.log('add', e, data);
|
||||
var that = $(this);
|
||||
|
||||
|
||||
// we need to collect all data upload objects before starting the upload so we can check their existence
|
||||
// and set individual conflict actions. unfortunately there is only one variable that we can use to identify
|
||||
// the selection a data upload is part of, so we have to collect them in data.originalFiles
|
||||
// turning singleFileUploads off is not an option because we want to gracefully handle server errors like
|
||||
// already exists
|
||||
|
||||
|
||||
// create a container where we can store the data objects
|
||||
if ( ! data.originalFiles.selection ) {
|
||||
// initialize selection and remember number of files to upload
|
||||
@@ -213,32 +213,40 @@ $(document).ready(function() {
|
||||
};
|
||||
}
|
||||
var selection = data.originalFiles.selection;
|
||||
|
||||
|
||||
// add uploads
|
||||
if ( selection.uploads.length < selection.filesToUpload ) {
|
||||
// remember upload
|
||||
selection.uploads.push(data);
|
||||
}
|
||||
|
||||
|
||||
//examine file
|
||||
var file = data.files[0];
|
||||
|
||||
if (file.type === '' && file.size === 4096) {
|
||||
try {
|
||||
// FIXME: not so elegant... need to refactor that method to return a value
|
||||
Files.isFileNameValid(file.name, FileList.getCurrentDirectory());
|
||||
}
|
||||
catch (errorMessage) {
|
||||
data.textStatus = 'invalidcharacters';
|
||||
data.errorThrown = errorMessage;
|
||||
}
|
||||
|
||||
if ((file.type === '' && file.size === 4096) || file.relativePath) {
|
||||
data.textStatus = 'dirorzero';
|
||||
data.errorThrown = t('files', 'Unable to upload {filename} as it is a directory or has 0 bytes',
|
||||
{filename: file.name}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// add size
|
||||
selection.totalBytes += file.size;
|
||||
|
||||
|
||||
//check max upload size
|
||||
if (selection.totalBytes > $('#max_upload').val()) {
|
||||
data.textStatus = 'notenoughspace';
|
||||
data.errorThrown = t('files', 'Not enough space available');
|
||||
}
|
||||
|
||||
|
||||
// end upload for whole selection on error
|
||||
if (data.errorThrown) {
|
||||
// trigger fileupload fail
|
||||
@@ -249,12 +257,12 @@ $(document).ready(function() {
|
||||
|
||||
// check existing files when all is collected
|
||||
if ( selection.uploads.length >= selection.filesToUpload ) {
|
||||
|
||||
|
||||
//remove our selection hack:
|
||||
delete data.originalFiles.selection;
|
||||
|
||||
var callbacks = {
|
||||
|
||||
|
||||
onNoConflicts: function (selection) {
|
||||
$.each(selection.uploads, function(i, upload) {
|
||||
upload.submit();
|
||||
@@ -277,7 +285,7 @@ $(document).ready(function() {
|
||||
};
|
||||
|
||||
OC.Upload.checkExistingFiles(selection, callbacks);
|
||||
|
||||
|
||||
}
|
||||
|
||||
return true; // continue adding files
|
||||
@@ -288,6 +296,8 @@ $(document).ready(function() {
|
||||
*/
|
||||
start: function(e) {
|
||||
OC.Upload.log('start', e, null);
|
||||
//hide the tooltip otherwise it covers the progress bar
|
||||
$('#upload').tipsy('hide');
|
||||
},
|
||||
submit: function(e, data) {
|
||||
OC.Upload.rememberUpload(data);
|
||||
@@ -400,7 +410,7 @@ $(document).ready(function() {
|
||||
});
|
||||
fileupload.on('fileuploadstop', function(e, data) {
|
||||
OC.Upload.log('progress handle fileuploadstop', e, data);
|
||||
|
||||
|
||||
$('#uploadprogresswrapper input.stop').fadeOut();
|
||||
$('#uploadprogressbar').fadeOut();
|
||||
Files.updateStorageStatistics();
|
||||
@@ -492,7 +502,7 @@ $(document).ready(function() {
|
||||
if ($(this).children('p').length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$('#new .error').tipsy('hide');
|
||||
|
||||
$('#new li').each(function(i,element) {
|
||||
@@ -506,7 +516,7 @@ $(document).ready(function() {
|
||||
var text=$(this).children('p').text();
|
||||
$(this).data('text',text);
|
||||
$(this).children('p').remove();
|
||||
|
||||
|
||||
// add input field
|
||||
var form = $('<form></form>');
|
||||
var input = $('<input type="text">');
|
||||
@@ -523,7 +533,7 @@ $(document).ready(function() {
|
||||
throw t('files', 'URL cannot be empty');
|
||||
} else if (type !== 'web' && !Files.isFileNameValid(filename)) {
|
||||
// Files.isFileNameValid(filename) throws an exception itself
|
||||
} else if ($('#dir').val() === '/' && filename === 'Shared') {
|
||||
} else if (FileList.getCurrentDirectory() === '/' && filename.toLowerCase() === 'shared') {
|
||||
throw t('files', 'In the home folder \'Shared\' is a reserved filename');
|
||||
} else if (FileList.inList(filename)) {
|
||||
throw t('files', '{new_name} already exists', {new_name: filename});
|
||||
@@ -605,7 +615,7 @@ $(document).ready(function() {
|
||||
if (result.status === 'success') {
|
||||
var date=new Date();
|
||||
FileList.addDir(name, 0, date, hidden);
|
||||
var tr=$('tr[data-file="'+name+'"]');
|
||||
var tr = FileList.findFileEl(name);
|
||||
tr.attr('data-id', result.data.id);
|
||||
} else {
|
||||
OC.dialogs.alert(result.data.message, t('core', 'Could not create folder'));
|
||||
@@ -647,7 +657,7 @@ $(document).ready(function() {
|
||||
$('#uploadprogressbar').fadeOut();
|
||||
var date = new Date();
|
||||
FileList.addFile(localName, size, date, false, hidden);
|
||||
var tr = $('tr[data-file="'+localName+'"]');
|
||||
var tr = FileList.findFileEl(localName);
|
||||
tr.data('mime', mime).data('id', id);
|
||||
tr.attr('data-id', id);
|
||||
var path = $('#dir').val()+'/'+localName;
|
||||
@@ -658,7 +668,11 @@ $(document).ready(function() {
|
||||
});
|
||||
eventSource.listen('error',function(error) {
|
||||
$('#uploadprogressbar').fadeOut();
|
||||
alert(error);
|
||||
OC.Notification.show(error);
|
||||
// hide notification after 10 sec
|
||||
setTimeout(function() {
|
||||
OC.Notification.hide();
|
||||
}, 10000);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ var FileActions = {
|
||||
FileActions.currentFile = parent;
|
||||
var actions = FileActions.get(FileActions.getCurrentMimeType(), FileActions.getCurrentType(), FileActions.getCurrentPermissions());
|
||||
var file = FileActions.getCurrentFile();
|
||||
if ($('tr[data-file="'+file+'"]').data('renaming')) {
|
||||
if (FileList.findFileEl(file).data('renaming')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,9 +103,9 @@ var FileActions = {
|
||||
}
|
||||
var html = '<a href="#" class="action" data-action="' + name + '">';
|
||||
if (img) {
|
||||
html += '<img class ="svg" src="' + img + '" /> ';
|
||||
html += '<img class ="svg" src="' + img + '" />';
|
||||
}
|
||||
html += t('files', name) + '</a>';
|
||||
html += '<span> ' + t('files', name) + '</span></a>';
|
||||
|
||||
var element = $(html);
|
||||
element.data('action', name);
|
||||
@@ -163,13 +163,18 @@ var FileActions = {
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
var hasDownloadAction = false;
|
||||
var downloadScope;
|
||||
if ($('#allowZipDownload').val() == 1) {
|
||||
var downloadScope = 'all';
|
||||
downloadScope = 'all';
|
||||
} else {
|
||||
var downloadScope = 'file';
|
||||
downloadScope = 'file';
|
||||
}
|
||||
|
||||
if (typeof disableDownloadActions == 'undefined' || !disableDownloadActions) {
|
||||
hasDownloadAction = FileActions.actions[downloadScope] && FileActions.actions[downloadScope].Download;
|
||||
|
||||
// do not override download action if it exists (might have been registered by an app already)
|
||||
if ((typeof disableDownloadActions == 'undefined' || !disableDownloadActions) && !hasDownloadAction) {
|
||||
FileActions.register(downloadScope, 'Download', OC.PERMISSION_READ, function () {
|
||||
return OC.imagePath('core', 'actions/download');
|
||||
}, function (filename) {
|
||||
|
||||
@@ -6,6 +6,13 @@ var FileList={
|
||||
$(this).attr('data-file',decodeURIComponent($(this).attr('data-file')));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Returns the tr element for a given file name
|
||||
*/
|
||||
findFileEl: function(fileName){
|
||||
// use filterAttr to avoid escaping issues
|
||||
return $('#fileList tr').filterAttr('data-file', fileName);
|
||||
},
|
||||
update:function(fileListHtml) {
|
||||
var $fileList = $('#fileList');
|
||||
$fileList.empty().html(fileListHtml);
|
||||
@@ -20,6 +27,8 @@ var FileList={
|
||||
Files.setupDragAndDrop();
|
||||
}
|
||||
FileList.updateFileSummary();
|
||||
procesSelection();
|
||||
|
||||
$fileList.trigger(jQuery.Event("updated"));
|
||||
},
|
||||
createRow:function(type, name, iconurl, linktarget, size, lastModified, permissions) {
|
||||
@@ -292,8 +301,12 @@ var FileList={
|
||||
$('#filestable').toggleClass('hidden', show);
|
||||
},
|
||||
remove:function(name){
|
||||
$('tr').filterAttr('data-file',name).find('td.filename').draggable('destroy');
|
||||
$('tr').filterAttr('data-file',name).remove();
|
||||
var fileEl = FileList.findFileEl(name);
|
||||
if (fileEl.data('permissions') & OC.PERMISSION_DELETE) {
|
||||
// file is only draggable when delete permissions are set
|
||||
fileEl.find('td.filename').draggable('destroy');
|
||||
}
|
||||
fileEl.remove();
|
||||
FileList.updateFileSummary();
|
||||
if ( ! $('tr[data-file]').exists() ) {
|
||||
$('#emptycontent').removeClass('hidden');
|
||||
@@ -334,7 +347,7 @@ var FileList={
|
||||
FileList.updateFileSummary();
|
||||
},
|
||||
loadingDone:function(name, id) {
|
||||
var mime, tr = $('tr[data-file="'+name+'"]');
|
||||
var mime, tr = FileList.findFileEl(name);
|
||||
tr.data('loading', false);
|
||||
mime = tr.data('mime');
|
||||
tr.attr('data-mime', mime);
|
||||
@@ -347,12 +360,12 @@ var FileList={
|
||||
}, null, null, tr.attr('data-etag'));
|
||||
tr.find('td.filename').draggable(dragOptions);
|
||||
},
|
||||
isLoading:function(name) {
|
||||
return $('tr[data-file="'+name+'"]').data('loading');
|
||||
isLoading:function(file) {
|
||||
return FileList.findFileEl(file).data('loading');
|
||||
},
|
||||
rename:function(oldname) {
|
||||
var tr, td, input, form;
|
||||
tr = $('tr[data-file="'+oldname+'"]');
|
||||
tr = FileList.findFileEl(oldname);
|
||||
tr.data('renaming',true);
|
||||
td = tr.children('td.filename');
|
||||
input = $('<input type="text" class="filename"/>').val(oldname);
|
||||
@@ -367,15 +380,12 @@ var FileList={
|
||||
len = input.val().length;
|
||||
}
|
||||
input.selectRange(0, len);
|
||||
|
||||
var checkInput = function () {
|
||||
var filename = input.val();
|
||||
if (filename !== oldname) {
|
||||
if (!Files.isFileNameValid(filename)) {
|
||||
// Files.isFileNameValid(filename) throws an exception itself
|
||||
} else if($('#dir').val() === '/' && filename === 'Shared') {
|
||||
throw t('files','In the home folder \'Shared\' is a reserved filename');
|
||||
} else if (FileList.inList(filename)) {
|
||||
// Files.isFileNameValid(filename) throws an exception itself
|
||||
Files.isFileNameValid(filename, FileList.getCurrentDirectory());
|
||||
if (FileList.inList(filename)) {
|
||||
throw t('files', '{new_name} already exists', {new_name: filename});
|
||||
}
|
||||
}
|
||||
@@ -500,14 +510,16 @@ var FileList={
|
||||
form.trigger('submit');
|
||||
});
|
||||
},
|
||||
inList:function(filename) {
|
||||
return $('#fileList tr[data-file="'+filename+'"]').length;
|
||||
inList:function(file) {
|
||||
return FileList.findFileEl(file).length;
|
||||
},
|
||||
replace:function(oldName, newName, isNewFile) {
|
||||
// Finish any existing actions
|
||||
$('tr[data-file="'+oldName+'"]').hide();
|
||||
$('tr[data-file="'+newName+'"]').hide();
|
||||
var tr = $('tr[data-file="'+oldName+'"]').clone();
|
||||
var oldFileEl = FileList.findFileEl(oldName);
|
||||
var newFileEl = FileList.findFileEl(newName);
|
||||
oldFileEl.hide();
|
||||
newFileEl.hide();
|
||||
var tr = oldFileEl.clone();
|
||||
tr.attr('data-replace', 'true');
|
||||
tr.attr('data-file', newName);
|
||||
var td = tr.children('td.filename');
|
||||
@@ -559,7 +571,7 @@ var FileList={
|
||||
files=[files];
|
||||
}
|
||||
for (var i=0; i<files.length; i++) {
|
||||
var deleteAction = $('tr[data-file="'+files[i]+'"]').children("td.date").children(".action.delete");
|
||||
var deleteAction = FileList.findFileEl(files[i]).children("td.date").children(".action.delete");
|
||||
deleteAction.removeClass('delete-icon').addClass('progress-icon');
|
||||
}
|
||||
// Finish any existing actions
|
||||
@@ -573,7 +585,7 @@ var FileList={
|
||||
function(result) {
|
||||
if (result.status === 'success') {
|
||||
$.each(files,function(index,file) {
|
||||
var files = $('tr[data-file="'+file+'"]');
|
||||
var files = FileList.findFileEl(file);
|
||||
files.remove();
|
||||
files.find('input[type="checkbox"]').removeAttr('checked');
|
||||
files.removeClass('selected');
|
||||
@@ -595,7 +607,7 @@ var FileList={
|
||||
OC.Notification.hide();
|
||||
}, 10000);
|
||||
$.each(files,function(index,file) {
|
||||
var deleteAction = $('tr[data-file="' + file + '"] .action.delete');
|
||||
var deleteAction = FileList.findFileEl(file).find('.action.delete');
|
||||
deleteAction.removeClass('progress-icon').addClass('delete-icon');
|
||||
});
|
||||
}
|
||||
@@ -737,7 +749,7 @@ var FileList={
|
||||
},
|
||||
scrollTo:function(file) {
|
||||
//scroll to and highlight preselected file
|
||||
var $scrolltorow = $('tr[data-file="'+file+'"]');
|
||||
var $scrolltorow = FileList.findFileEl(file);
|
||||
if ($scrolltorow.exists()) {
|
||||
$scrolltorow.addClass('searchresult');
|
||||
$(window).scrollTop($scrolltorow.position().top);
|
||||
@@ -880,8 +892,8 @@ $(document).ready(function() {
|
||||
data.context.find('td.filesize').text(humanFileSize(size));
|
||||
|
||||
} else {
|
||||
// only append new file if dragged onto current dir's crumb (last)
|
||||
if (data.context && data.context.hasClass('crumb') && !data.context.hasClass('last')) {
|
||||
// only append new file if uploaded into the current folder
|
||||
if (file.directory !== FileList.getCurrentDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -949,7 +961,7 @@ $(document).ready(function() {
|
||||
$('#notification').on('click', '.undo', function() {
|
||||
if (FileList.deleteFiles) {
|
||||
$.each(FileList.deleteFiles,function(index,file) {
|
||||
$('tr[data-file="'+file+'"]').show();
|
||||
FileList.findFileEl(file).show();
|
||||
});
|
||||
FileList.deleteCanceled=true;
|
||||
FileList.deleteFiles=null;
|
||||
@@ -959,10 +971,10 @@ $(document).ready(function() {
|
||||
FileList.deleteCanceled = false;
|
||||
FileList.deleteFiles = [FileList.replaceOldName];
|
||||
} else {
|
||||
$('tr[data-file="'+FileList.replaceOldName+'"]').show();
|
||||
FileList.findFileEl(FileList.replaceOldName).show();
|
||||
}
|
||||
$('tr[data-replace="true"').remove();
|
||||
$('tr[data-file="'+FileList.replaceNewName+'"]').show();
|
||||
FileList.findFileEl(FileList.replaceNewName).show();
|
||||
FileList.replaceCanceled = true;
|
||||
FileList.replaceOldName = null;
|
||||
FileList.replaceNewName = null;
|
||||
@@ -977,7 +989,8 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
$('#notification:first-child').on('click', '.suggest', function() {
|
||||
$('tr[data-file="'+$('#notification > span').attr('data-oldName')+'"]').show();
|
||||
var file = $('#notification > span').attr('data-oldName');
|
||||
FileList.findFileEl(file).show();
|
||||
OC.Notification.hide();
|
||||
});
|
||||
$('#notification:first-child').on('click', '.cancel', function() {
|
||||
|
||||
@@ -67,10 +67,21 @@ Files={
|
||||
return fileName;
|
||||
},
|
||||
|
||||
isFileNameValid:function (name) {
|
||||
if (name === '.') {
|
||||
throw t('files', '\'.\' is an invalid file name.');
|
||||
} else if (name.length === 0) {
|
||||
/**
|
||||
* Checks whether the given file name is valid.
|
||||
* @param name file name to check
|
||||
* @return true if the file name is valid.
|
||||
* Throws a string exception with an error message if
|
||||
* the file name is not valid
|
||||
*/
|
||||
isFileNameValid: function (name, root) {
|
||||
var trimmedName = name.trim();
|
||||
if (trimmedName === '.'
|
||||
|| trimmedName === '..'
|
||||
|| (root === '/' && trimmedName.toLowerCase() === 'shared'))
|
||||
{
|
||||
throw t('files', '"{name}" is an invalid file name.', {name: name});
|
||||
} else if (trimmedName.length === 0) {
|
||||
throw t('files', 'File name cannot be empty.');
|
||||
}
|
||||
|
||||
@@ -282,7 +293,7 @@ $(document).ready(function() {
|
||||
procesSelection();
|
||||
} else {
|
||||
var filename=$(this).parent().parent().attr('data-file');
|
||||
var tr=$('tr[data-file="'+filename+'"]');
|
||||
var tr = FileList.findFileEl(filename);
|
||||
var renaming=tr.data('renaming');
|
||||
if (!renaming && !FileList.isLoading(filename)) {
|
||||
FileActions.currentFile = $(this).parent();
|
||||
@@ -350,7 +361,12 @@ $(document).ready(function() {
|
||||
// use special download URL if provided, e.g. for public shared files
|
||||
var downloadURL = document.getElementById("downloadURL");
|
||||
if ( downloadURL ) {
|
||||
window.location = downloadURL.value+"&download&files=" + encodeURIComponent(fileslist);
|
||||
// downloading all in root of public share ? (replacement for old "Download" button)
|
||||
if ($('#isPublic').val() && dir === '/' && $('#select_all').is(':checked')) {
|
||||
window.location = downloadURL.value;
|
||||
} else {
|
||||
window.location = downloadURL.value+"&download&files=" + encodeURIComponent(fileslist);
|
||||
}
|
||||
} else {
|
||||
window.location = OC.filePath('files', 'ajax', 'download.php') + '?'+ $.param({ dir: dir, files: fileslist });
|
||||
}
|
||||
@@ -541,10 +557,12 @@ var folderDropOptions={
|
||||
if (result) {
|
||||
if (result.status === 'success') {
|
||||
//recalculate folder size
|
||||
var oldSize = $('#fileList tr[data-file="'+target+'"]').data('size');
|
||||
var newSize = oldSize + $('#fileList tr[data-file="'+file+'"]').data('size');
|
||||
$('#fileList tr[data-file="'+target+'"]').data('size', newSize);
|
||||
$('#fileList tr[data-file="'+target+'"]').find('td.filesize').text(humanFileSize(newSize));
|
||||
var oldFile = FileList.findFileEl(target);
|
||||
var newFile = FileList.findFileEl(file);
|
||||
var oldSize = oldFile.data('size');
|
||||
var newSize = oldSize + newFile.data('size');
|
||||
oldFile.data('size', newSize);
|
||||
oldFile.find('td.filesize').text(humanFileSize(newSize));
|
||||
|
||||
FileList.remove(file);
|
||||
procesSelection();
|
||||
@@ -610,11 +628,12 @@ function procesSelection() {
|
||||
return el.type==='dir';
|
||||
});
|
||||
if (selectedFiles.length === 0 && selectedFolders.length === 0) {
|
||||
$('#headerName>span.name').text(t('files','Name'));
|
||||
$('#headerName span.name').text(t('files','Name'));
|
||||
$('#headerSize').text(t('files','Size'));
|
||||
$('#modified').text(t('files','Modified'));
|
||||
$('table').removeClass('multiselect');
|
||||
$('.selectedActions').hide();
|
||||
$('#select_all').removeAttr('checked');
|
||||
}
|
||||
else {
|
||||
$('.selectedActions').show();
|
||||
@@ -717,7 +736,7 @@ Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) {
|
||||
console.warn('Files.lazyLoadPreview(): missing etag argument');
|
||||
}
|
||||
|
||||
if ( $('#publicUploadButtonMock').length ) {
|
||||
if ( $('#isPublic').length ) {
|
||||
urlSpec.t = $('#dirToken').val();
|
||||
previewURL = OC.Router.generate('core_ajax_public_preview', urlSpec);
|
||||
} else {
|
||||
@@ -738,7 +757,7 @@ Files.lazyLoadPreview = function(path, mime, ready, width, height, etag) {
|
||||
}
|
||||
|
||||
function getUniqueName(name) {
|
||||
if ($('tr[data-file="'+name+'"]').exists()) {
|
||||
if (FileList.findFileEl(name).exists()) {
|
||||
var parts=name.split('.');
|
||||
var extension = "";
|
||||
if (parts.length > 1) {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%n folder_::_%n folders_" => array("",""),
|
||||
"_%n file_::_%n files_" => array("",""),
|
||||
"_Uploading %n file_::_Uploading %n files_" => array("","")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
@@ -1,5 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"Save" => "Zapisz"
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);";
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"Files" => "Файлы",
|
||||
"Share" => "Сделать общим",
|
||||
"Rename" => "Переименовать",
|
||||
"_%n folder_::_%n folders_" => array("","",""),
|
||||
"_%n file_::_%n files_" => array("","",""),
|
||||
"_Uploading %n file_::_Uploading %n files_" => array("","",""),
|
||||
"Error" => "Ошибка",
|
||||
"Size" => "Размер",
|
||||
"Upload" => "Загрузка",
|
||||
"Save" => "Сохранить",
|
||||
"Cancel upload" => "Отмена загрузки",
|
||||
"Download" => "Загрузка",
|
||||
"Delete" => "Удалить"
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);";
|
||||
@@ -15,7 +15,8 @@ class Helper
|
||||
|
||||
return array('uploadMaxFilesize' => $maxUploadFilesize,
|
||||
'maxHumanFilesize' => $maxHumanFilesize,
|
||||
'usedSpacePercent' => (int)$storageInfo['relative']);
|
||||
'usedSpacePercent' => (int)$storageInfo['relative'],
|
||||
'freeSpace' => (int)$storageInfo['free']);
|
||||
}
|
||||
|
||||
public static function determineIcon($file) {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<span class="what">{what}<!-- If you select both versions, the copied file will have a number added to its name. --></span><br/>
|
||||
<br/>
|
||||
<table>
|
||||
<th><label><input class="allnewfiles" type="checkbox" />New Files<span class="count"></span></label></th>
|
||||
<th><label><input class="allexistingfiles" type="checkbox" />Already existing files<span class="count"></span></label></th>
|
||||
<th><label><input class="allnewfiles" type="checkbox" />{allnewfiles}<span class="count"></span></label></th>
|
||||
<th><label><input class="allexistingfiles" type="checkbox" />{allexistingfiles}<span class="count"></span></label></th>
|
||||
</table>
|
||||
<div class="conflicts">
|
||||
<div class="template">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<div id="controls">
|
||||
<?php print_unescaped($_['breadcrumb']); ?>
|
||||
<div class="actions creatable <?php if (!$_['isCreatable']):?>hidden<?php endif; ?>">
|
||||
<?php if(!isset($_['dirToken'])):?>
|
||||
<div id="new" class="button">
|
||||
<a><?php p($l->t('New'));?></a>
|
||||
<ul>
|
||||
@@ -12,21 +13,26 @@
|
||||
data-type='web'><p><?php p($l->t('From link'));?></p></li>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif;?>
|
||||
<div id="upload" class="button"
|
||||
title="<?php p($l->t('Upload') . ' max. '.$_['uploadMaxHumanFilesize']) ?>">
|
||||
<?php if($_['uploadMaxFilesize'] >= 0):?>
|
||||
<input type="hidden" name="MAX_FILE_SIZE" id="max_upload"
|
||||
value="<?php p($_['uploadMaxFilesize']) ?>">
|
||||
<?php endif;?>
|
||||
<?php if(isset($_['dirToken'])):?>
|
||||
<input type="hidden" id="publicUploadRequestToken" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
|
||||
<input type="hidden" id="dirToken" name="dirToken" value="<?php p($_['dirToken']) ?>" />
|
||||
<?php endif;?>
|
||||
<input type="hidden" class="max_human_file_size"
|
||||
value="(max <?php p($_['uploadMaxHumanFilesize']); ?>)">
|
||||
<input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir">
|
||||
<input type="file" id="file_upload_start" name='files[]'
|
||||
data-url="<?php print_unescaped(OCP\Util::linkTo('files', 'ajax/upload.php')); ?>" />
|
||||
<a href="#" class="svg"></a>
|
||||
<a href="#" class="svg icon icon-upload"></a>
|
||||
</div>
|
||||
<?php if ($_['trash']): ?>
|
||||
<input id="trash" type="button" value="<?php p($l->t('Deleted files'));?>" class="button" <?php $_['trashEmpty'] ? p('disabled') : '' ?>></input>
|
||||
<input id="trash" type="button" value="<?php p($l->t('Deleted files'));?>" class="button" <?php $_['trashEmpty'] ? p('disabled') : '' ?> />
|
||||
<?php endif; ?>
|
||||
<div id="uploadprogresswrapper">
|
||||
<div id="uploadprogressbar"></div>
|
||||
@@ -44,7 +50,7 @@
|
||||
|
||||
<div id="emptycontent" <?php if (!$_['emptyContent']):?>class="hidden"<?php endif; ?>><?php p($l->t('Nothing in here. Upload something!'))?></div>
|
||||
|
||||
<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>"></input>
|
||||
<input type="hidden" id="disableSharing" data-status="<?php p($_['disableSharing']); ?>" />
|
||||
|
||||
<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36">
|
||||
<thead>
|
||||
@@ -69,20 +75,11 @@
|
||||
<th <?php if (!$_['fileHeader']):?>class="hidden"<?php endif; ?> id="headerDate">
|
||||
<span id="modified"><?php p($l->t( 'Modified' )); ?></span>
|
||||
<?php if ($_['permissions'] & OCP\PERMISSION_DELETE): ?>
|
||||
<!-- NOTE: Temporary fix to allow unsharing of files in root of Shared folder -->
|
||||
<?php if ($_['dir'] == '/Shared'): ?>
|
||||
<span class="selectedActions"><a href="" class="delete-selected">
|
||||
<?php p($l->t('Unshare'))?>
|
||||
<img class="svg" alt="<?php p($l->t('Unshare'))?>"
|
||||
src="<?php print_unescaped(OCP\image_path("core", "actions/delete.svg")); ?>" />
|
||||
</a></span>
|
||||
<?php else: ?>
|
||||
<span class="selectedActions"><a href="" class="delete-selected">
|
||||
<?php p($l->t('Delete'))?>
|
||||
<img class="svg" alt="<?php p($l->t('Delete'))?>"
|
||||
src="<?php print_unescaped(OCP\image_path("core", "actions/delete.svg")); ?>" />
|
||||
</a></span>
|
||||
<?php endif; ?>
|
||||
<span class="selectedActions"><a href="" class="delete-selected">
|
||||
<?php p($l->t('Delete'))?>
|
||||
<img class="svg" alt="<?php p($l->t('Delete'))?>"
|
||||
src="<?php print_unescaped(OCP\image_path("core", "actions/delete.svg")); ?>" />
|
||||
</a></span>
|
||||
<?php endif; ?>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<?php if(count($_["breadcrumb"])):?>
|
||||
<div class="crumb" data-dir=''>
|
||||
<a href="<?php print_unescaped($_['baseURL']); ?>">
|
||||
<img src="<?php print_unescaped(OCP\image_path('core', 'places/home.svg'));?>" class="svg" />
|
||||
</a>
|
||||
</div>
|
||||
<?php endif;?>
|
||||
<div class="crumb <?php if(!count($_["breadcrumb"])) p('last');?>" data-dir=''>
|
||||
<a href="<?php print_unescaped($_['baseURL']); ?>">
|
||||
<img src="<?php print_unescaped(OCP\image_path('core', 'places/home.svg'));?>" class="svg" />
|
||||
</a>
|
||||
</div>
|
||||
<?php for($i=0; $i<count($_["breadcrumb"]); $i++):
|
||||
$crumb = $_["breadcrumb"][$i];
|
||||
$dir = \OCP\Util::encodePath($crumb["dir"]); ?>
|
||||
|
||||
@@ -18,7 +18,7 @@ $totalsize = 0; ?>
|
||||
data-size="<?php p($file['size']);?>"
|
||||
data-etag="<?php p($file['etag']);?>"
|
||||
data-permissions="<?php p($file['permissions']); ?>">
|
||||
<?php if($file['isPreviewAvailable']): ?>
|
||||
<?php if(isset($file['isPreviewAvailable']) and $file['isPreviewAvailable']): ?>
|
||||
<td class="filename svg preview-icon"
|
||||
<?php else: ?>
|
||||
<td class="filename svg"
|
||||
@@ -34,17 +34,15 @@ $totalsize = 0; ?>
|
||||
<span class="nametext">
|
||||
<?php print_unescaped(htmlspecialchars($file['name']));?>
|
||||
</span>
|
||||
<span class="uploadtext" currentUploads="0">
|
||||
</span>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a class="name" href="<?php p(rtrim($_['downloadURL'],'/').'/'.trim($directory,'/').'/'.$name); ?>">
|
||||
<label class="filetext" title="" for="select-<?php p($file['fileid']); ?>"></label>
|
||||
<span class="nametext"><?php print_unescaped(htmlspecialchars($file['basename']));?><span class='extension'><?php p($file['extension']);?></span></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if($file['type'] == 'dir'):?>
|
||||
<span class="uploadtext" currentUploads="0">
|
||||
</span>
|
||||
<?php endif;?>
|
||||
</a>
|
||||
</td>
|
||||
<td class="filesize"
|
||||
style="color:rgb(<?php p($simple_size_color.','.$simple_size_color.','.$simple_size_color) ?>)">
|
||||
|
||||
@@ -6,6 +6,7 @@ if (OC::$CLI) {
|
||||
if (count($argv) === 2) {
|
||||
$file = $argv[1];
|
||||
list(, $user) = explode('/', $file);
|
||||
OCP\JSON::checkUserExists($owner);
|
||||
OC_Util::setupFS($user);
|
||||
$view = new \OC\Files\View('');
|
||||
/**
|
||||
|
||||
@@ -13,16 +13,14 @@ use OCA\Encryption\Util;
|
||||
$loginname = isset($_POST['user']) ? $_POST['user'] : '';
|
||||
$password = isset($_POST['password']) ? $_POST['password'] : '';
|
||||
|
||||
$migrationCompleted = true;
|
||||
$migrationStatus = Util::MIGRATION_COMPLETED;
|
||||
|
||||
if ($loginname !== '' && $password !== '') {
|
||||
$username = \OCP\User::checkPassword($loginname, $password);
|
||||
if ($username) {
|
||||
$util = new Util(new \OC_FilesystemView('/'), $username);
|
||||
if ($util->getMigrationStatus() !== Util::MIGRATION_COMPLETED) {
|
||||
$migrationCompleted = false;
|
||||
}
|
||||
$migrationStatus = $util->getMigrationStatus();
|
||||
}
|
||||
}
|
||||
|
||||
\OCP\JSON::success(array('data' => array('migrationCompleted' => $migrationCompleted)));
|
||||
\OCP\JSON::success(array('data' => array('migrationStatus' => $migrationStatus)));
|
||||
|
||||
@@ -10,6 +10,7 @@ OC::$CLASSPATH['OCA\Encryption\Session'] = 'files_encryption/lib/session.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Capabilities'] = 'files_encryption/lib/capabilities.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Helper'] = 'files_encryption/lib/helper.php';
|
||||
|
||||
\OCP\Util::addscript('files_encryption', 'encryption');
|
||||
\OCP\Util::addscript('files_encryption', 'detect-migration');
|
||||
|
||||
if (!OC_Config::getValue('maintenance', false)) {
|
||||
|
||||
@@ -18,22 +18,19 @@
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>64</length>
|
||||
<comments>What client-side / server-side configuration is used</comments>
|
||||
</field>
|
||||
<field>
|
||||
<name>recovery_enabled</name>
|
||||
<type>integer</type>
|
||||
<notnull>true</notnull>
|
||||
<default>0</default>
|
||||
<comments>Whether encryption key recovery is enabled</comments>
|
||||
</field>
|
||||
<field>
|
||||
<name>migration_status</name>
|
||||
<type>integer</type>
|
||||
<notnull>true</notnull>
|
||||
<default>0</default>
|
||||
<comments>Whether encryption migration has been performed</comments>
|
||||
</field>
|
||||
</declaration>
|
||||
</table>
|
||||
</database>
|
||||
</database>
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
<info>
|
||||
<id>files_encryption</id>
|
||||
<name>Encryption</name>
|
||||
<description>The new ownCloud 5 files encryption system. After the app was enabled you need to re-login to initialize your encryption keys.</description>
|
||||
<description>The ownCloud files encryption system provides server side-encryption. After the app is enabled you need to re-login to initialize your encryption keys. Please note that server side encryption requires that the ownCloud server admin can be trusted. The main purpose of this app is the encryption of files that are stored on externally mounted storages.</description>
|
||||
<licence>AGPL</licence>
|
||||
<author>Sam Tuke, Bjoern Schiessle, Florin Peter</author>
|
||||
<require>4</require>
|
||||
<shipped>true</shipped>
|
||||
<documentation>
|
||||
<user>http://doc.owncloud.org/server/6.0/user_manual/files/encryption.html</user>
|
||||
<admin>http://doc.owncloud.org/server/6.0/admin_manual/configuration/configuration_encryption.html</admin>
|
||||
</documentation>
|
||||
<rememberlogin>false</rememberlogin>
|
||||
<types>
|
||||
<filesystem/>
|
||||
|
||||
@@ -5,6 +5,9 @@ if (!isset($_)) { //also provide standalone error page
|
||||
|
||||
$l = OC_L10N::get('files_encryption');
|
||||
|
||||
OC_JSON::checkAppEnabled('files_encryption');
|
||||
OC_App::loadApp('files_encryption');
|
||||
|
||||
if (isset($_GET['errorCode'])) {
|
||||
$errorCode = $_GET['errorCode'];
|
||||
switch ($errorCode) {
|
||||
|
||||
@@ -30,6 +30,11 @@ use OC\Files\Filesystem;
|
||||
*/
|
||||
class Hooks {
|
||||
|
||||
// file for which we want to rename the keys after the rename operation was successful
|
||||
private static $renamedFiles = array();
|
||||
// file for which we want to delete the keys after the delete operation was successful
|
||||
private static $deleteFiles = array();
|
||||
|
||||
/**
|
||||
* @brief Startup encryption backend upon user login
|
||||
* @note This method should never be called for users using client side encryption
|
||||
@@ -75,8 +80,14 @@ class Hooks {
|
||||
|
||||
// Check if first-run file migration has already been performed
|
||||
$ready = false;
|
||||
if ($util->getMigrationStatus() === Util::MIGRATION_OPEN) {
|
||||
$migrationStatus = $util->getMigrationStatus();
|
||||
if ($migrationStatus === Util::MIGRATION_OPEN && $session !== false) {
|
||||
$ready = $util->beginMigration();
|
||||
} elseif ($migrationStatus === Util::MIGRATION_IN_PROGRESS) {
|
||||
// refuse login as long as the initial encryption is running
|
||||
sleep(5);
|
||||
\OCP\User::logout();
|
||||
return false;
|
||||
}
|
||||
|
||||
// If migration not yet done
|
||||
@@ -90,28 +101,32 @@ class Hooks {
|
||||
$userView->file_exists('encryption.key')
|
||||
&& $encLegacyKey = $userView->file_get_contents('encryption.key')
|
||||
) {
|
||||
$plainLegacyKey = Crypt::legacyDecrypt($encLegacyKey, $params['password']);
|
||||
|
||||
$plainLegacyKey = Crypt::legacyDecrypt($encLegacyKey, $params['password']);
|
||||
|
||||
$session->setLegacyKey($plainLegacyKey);
|
||||
|
||||
$session->setLegacyKey($plainLegacyKey);
|
||||
}
|
||||
|
||||
// Encrypt existing user files:
|
||||
if (
|
||||
$util->encryptAll('/' . $params['uid'] . '/' . 'files', $session->getLegacyKey(), $params['password'])
|
||||
) {
|
||||
// Encrypt existing user files
|
||||
try {
|
||||
$result = $util->encryptAll('/' . $params['uid'] . '/' . 'files', $session->getLegacyKey(), $params['password']);
|
||||
} catch (\Exception $ex) {
|
||||
\OCP\Util::writeLog('Encryption library', 'Initial encryption failed! Error: ' . $ex->getMessage(), \OCP\Util::FATAL);
|
||||
$util->resetMigrationStatus();
|
||||
\OCP\User::logout();
|
||||
$result = false;
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
|
||||
\OC_Log::write(
|
||||
'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed'
|
||||
, \OC_Log::INFO
|
||||
);
|
||||
|
||||
// Register successful migration in DB
|
||||
$util->finishMigration();
|
||||
|
||||
}
|
||||
|
||||
// Register successful migration in DB
|
||||
$util->finishMigration();
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -179,20 +194,23 @@ class Hooks {
|
||||
// the necessary keys)
|
||||
if (Crypt::mode() === 'server') {
|
||||
|
||||
if ($params['uid'] === \OCP\User::getUser()) {
|
||||
$view = new \OC_FilesystemView('/');
|
||||
$session = new \OCA\Encryption\Session($view);
|
||||
|
||||
$view = new \OC_FilesystemView('/');
|
||||
// Get existing decrypted private key
|
||||
$privateKey = $session->getPrivateKey();
|
||||
|
||||
$session = new \OCA\Encryption\Session($view);
|
||||
|
||||
// Get existing decrypted private key
|
||||
$privateKey = $session->getPrivateKey();
|
||||
if ($params['uid'] === \OCP\User::getUser() && $privateKey) {
|
||||
|
||||
// Encrypt private key with new user pwd as passphrase
|
||||
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password']);
|
||||
|
||||
// Save private key
|
||||
Keymanager::setPrivateKey($encryptedPrivateKey);
|
||||
if ($encryptedPrivateKey) {
|
||||
Keymanager::setPrivateKey($encryptedPrivateKey);
|
||||
} else {
|
||||
\OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
|
||||
}
|
||||
|
||||
// NOTE: Session does not need to be updated as the
|
||||
// private key has not changed, only the passphrase
|
||||
@@ -202,36 +220,48 @@ class Hooks {
|
||||
} else { // admin changed the password for a different user, create new keys and reencrypt file keys
|
||||
|
||||
$user = $params['uid'];
|
||||
$recoveryPassword = $params['recoveryPassword'];
|
||||
$newUserPassword = $params['password'];
|
||||
$util = new Util($view, $user);
|
||||
$recoveryPassword = isset($params['recoveryPassword']) ? $params['recoveryPassword'] : null;
|
||||
|
||||
$view = new \OC_FilesystemView('/');
|
||||
// we generate new keys if...
|
||||
// ...we have a recovery password and the user enabled the recovery key
|
||||
// ...encryption was activated for the first time (no keys exists)
|
||||
// ...the user doesn't have any files
|
||||
if (($util->recoveryEnabledForUser() && $recoveryPassword)
|
||||
|| !$util->userKeysExists()
|
||||
|| !$view->file_exists($user . '/files')) {
|
||||
|
||||
// make sure that the users home is mounted
|
||||
\OC\Files\Filesystem::initMountPoints($user);
|
||||
// backup old keys
|
||||
$util->backupAllKeys('recovery');
|
||||
|
||||
$keypair = Crypt::createKeypair();
|
||||
$newUserPassword = $params['password'];
|
||||
|
||||
// Disable encryption proxy to prevent recursive calls
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
// make sure that the users home is mounted
|
||||
\OC\Files\Filesystem::initMountPoints($user);
|
||||
|
||||
// Save public key
|
||||
$view->file_put_contents('/public-keys/' . $user . '.public.key', $keypair['publicKey']);
|
||||
$keypair = Crypt::createKeypair();
|
||||
|
||||
// Encrypt private key empty passphrase
|
||||
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword);
|
||||
// Disable encryption proxy to prevent recursive calls
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
// Save private key
|
||||
$view->file_put_contents(
|
||||
'/' . $user . '/files_encryption/' . $user . '.private.key', $encryptedPrivateKey);
|
||||
// Save public key
|
||||
$view->file_put_contents('/public-keys/' . $user . '.public.key', $keypair['publicKey']);
|
||||
|
||||
if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
|
||||
$util = new Util($view, $user);
|
||||
$util->recoverUsersFiles($recoveryPassword);
|
||||
// Encrypt private key empty passphrase
|
||||
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword);
|
||||
|
||||
// Save private key
|
||||
$view->file_put_contents(
|
||||
'/' . $user . '/files_encryption/' . $user . '.private.key', $encryptedPrivateKey);
|
||||
|
||||
if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
|
||||
$util = new Util($view, $user);
|
||||
$util->recoverUsersFiles($recoveryPassword);
|
||||
}
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
}
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,7 +336,6 @@ class Hooks {
|
||||
if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
|
||||
|
||||
$view = new \OC_FilesystemView('/');
|
||||
$session = new \OCA\Encryption\Session($view);
|
||||
$userId = \OCP\User::getUser();
|
||||
$util = new Util($view, $userId);
|
||||
$path = $util->fileIdToPath($params['itemSource']);
|
||||
@@ -363,25 +392,41 @@ class Hooks {
|
||||
}
|
||||
}
|
||||
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
|
||||
// get the path including mount point only if not a shared folder
|
||||
if (strncmp($path, '/Shared', strlen('/Shared') !== 0)) {
|
||||
// get path including the the storage mount point
|
||||
$path = $util->getPathWithMountPoint($params['itemSource']);
|
||||
}
|
||||
|
||||
// if a folder was shared, get a list of all (sub-)folders
|
||||
if ($params['itemType'] === 'folder') {
|
||||
$allFiles = $util->getAllFiles($path);
|
||||
} else {
|
||||
$allFiles = array($path);
|
||||
}
|
||||
self::updateKeyfiles($path, $params['itemType']);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($allFiles as $path) {
|
||||
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $path);
|
||||
$util->setSharedFileKeyfiles($session, $usersSharing, $path);
|
||||
}
|
||||
/**
|
||||
* update keyfiles and share keys recursively
|
||||
*
|
||||
* @param string $path to the file/folder
|
||||
* @param string $type 'file' or 'folder'
|
||||
*/
|
||||
private static function updateKeyfiles($path, $type) {
|
||||
|
||||
$view = new \OC_FilesystemView('/');
|
||||
$session = new \OCA\Encryption\Session($view);
|
||||
$userId = \OCP\User::getUser();
|
||||
$util = new Util($view, $userId);
|
||||
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
|
||||
// if a folder was shared, get a list of all (sub-)folders
|
||||
if ($type === 'folder') {
|
||||
$allFiles = $util->getAllFiles($path);
|
||||
} else {
|
||||
$allFiles = array($path);
|
||||
}
|
||||
|
||||
foreach ($allFiles as $path) {
|
||||
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $path);
|
||||
$util->setSharedFileKeyfiles($session, $usersSharing, $path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,13 +520,63 @@ class Hooks {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief after a file is renamed, rename its keyfile and share-keys also fix the file size and fix also the sharing
|
||||
* @param array with oldpath and newpath
|
||||
*
|
||||
* This function is connected to the rename signal of OC_Filesystem and adjust the name and location
|
||||
* of the stored versions along the actual file
|
||||
* @brief mark file as renamed so that we know the original source after the file was renamed
|
||||
* @param array $params with the old path and the new path
|
||||
*/
|
||||
public static function postRename($params) {
|
||||
public static function preRename($params) {
|
||||
$user = \OCP\User::getUser();
|
||||
$view = new \OC_FilesystemView('/');
|
||||
$util = new Util($view, $user);
|
||||
list($ownerOld, $pathOld) = $util->getUidAndFilename($params['oldpath']);
|
||||
|
||||
// we only need to rename the keys if the rename happens on the same mountpoint
|
||||
// otherwise we perform a stream copy, so we get a new set of keys
|
||||
$mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
|
||||
$mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
|
||||
$type = $view->is_dir('/' . $user . '/files/' . $params['oldpath']) ? 'folder' : 'file';
|
||||
if ($mp1 === $mp2) {
|
||||
self::$renamedFiles[$params['oldpath']] = array(
|
||||
'uid' => $ownerOld,
|
||||
'path' => $pathOld,
|
||||
'type' => $type,
|
||||
'operation' => 'rename',
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mark file as renamed so that we know the original source after the file was renamed
|
||||
* @param array $params with the old path and the new path
|
||||
*/
|
||||
public static function preCopy($params) {
|
||||
$user = \OCP\User::getUser();
|
||||
$view = new \OC\Files\View('/');
|
||||
$util = new Util($view, $user);
|
||||
list($ownerOld, $pathOld) = $util->getUidAndFilename($params['oldpath']);
|
||||
|
||||
// we only need to rename the keys if the rename happens on the same mountpoint
|
||||
// otherwise we perform a stream copy, so we get a new set of keys
|
||||
$mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
|
||||
$mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
|
||||
|
||||
$type = $view->is_dir('/' . $user . '/files/' . $params['oldpath']) ? 'folder' : 'file';
|
||||
|
||||
if ($mp1 === $mp2) {
|
||||
self::$renamedFiles[$params['oldpath']] = array(
|
||||
'uid' => $ownerOld,
|
||||
'path' => $pathOld,
|
||||
'type' => $type,
|
||||
'operation' => 'copy');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* after a file is renamed/copied, rename/copy its keyfile and share-keys also fix the file size and fix also the sharing
|
||||
*
|
||||
* @param array $params array with oldpath and newpath
|
||||
*/
|
||||
public static function postRenameOrCopy($params) {
|
||||
|
||||
if (\OCP\App::isEnabled('files_encryption') === false) {
|
||||
return true;
|
||||
@@ -492,56 +587,75 @@ class Hooks {
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$view = new \OC_FilesystemView('/');
|
||||
$session = new \OCA\Encryption\Session($view);
|
||||
$userId = \OCP\User::getUser();
|
||||
$util = new Util($view, $userId);
|
||||
|
||||
// Format paths to be relative to user files dir
|
||||
if ($util->isSystemWideMountPoint($params['oldpath'])) {
|
||||
$baseDir = 'files_encryption/';
|
||||
$oldKeyfilePath = $baseDir . 'keyfiles/' . $params['oldpath'];
|
||||
if (isset(self::$renamedFiles[$params['oldpath']]['uid']) &&
|
||||
isset(self::$renamedFiles[$params['oldpath']]['path'])) {
|
||||
$ownerOld = self::$renamedFiles[$params['oldpath']]['uid'];
|
||||
$pathOld = self::$renamedFiles[$params['oldpath']]['path'];
|
||||
$type = self::$renamedFiles[$params['oldpath']]['type'];
|
||||
$operation = self::$renamedFiles[$params['oldpath']]['operation'];
|
||||
unset(self::$renamedFiles[$params['oldpath']]);
|
||||
} else {
|
||||
$baseDir = $userId . '/' . 'files_encryption/';
|
||||
$oldKeyfilePath = $baseDir . 'keyfiles/' . $params['oldpath'];
|
||||
\OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::DEBUG);
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($util->isSystemWideMountPoint($params['newpath'])) {
|
||||
$newKeyfilePath = $baseDir . 'keyfiles/' . $params['newpath'];
|
||||
list($ownerNew, $pathNew) = $util->getUidAndFilename($params['newpath']);
|
||||
|
||||
// Format paths to be relative to user files dir
|
||||
if ($util->isSystemWideMountPoint($pathOld)) {
|
||||
$oldKeyfilePath = 'files_encryption/keyfiles/' . $pathOld;
|
||||
$oldShareKeyPath = 'files_encryption/share-keys/' . $pathOld;
|
||||
} else {
|
||||
$newKeyfilePath = $baseDir . 'keyfiles/' . $params['newpath'];
|
||||
$oldKeyfilePath = $ownerOld . '/' . 'files_encryption/keyfiles/' . $pathOld;
|
||||
$oldShareKeyPath = $ownerOld . '/' . 'files_encryption/share-keys/' . $pathOld;
|
||||
}
|
||||
|
||||
if ($util->isSystemWideMountPoint($pathNew)) {
|
||||
$newKeyfilePath = 'files_encryption/keyfiles/' . $pathNew;
|
||||
$newShareKeyPath = 'files_encryption/share-keys/' . $pathNew;
|
||||
} else {
|
||||
$newKeyfilePath = $ownerNew . '/files_encryption/keyfiles/' . $pathNew;
|
||||
$newShareKeyPath = $ownerNew . '/files_encryption/share-keys/' . $pathNew;
|
||||
}
|
||||
|
||||
// add key ext if this is not an folder
|
||||
if (!$view->is_dir($oldKeyfilePath)) {
|
||||
if ($type === 'file') {
|
||||
$oldKeyfilePath .= '.key';
|
||||
$newKeyfilePath .= '.key';
|
||||
|
||||
// create destination folder if not exists
|
||||
$localKeyPath = $view->getLocalFile($oldShareKeyPath);
|
||||
$newLocalKeyPath = \OC\Files\Filesystem::normalizePath(str_replace($pathOld, $pathNew, $localKeyPath));
|
||||
if (!file_exists(dirname($newLocalKeyPath))) {
|
||||
mkdir(dirname($newLocalKeyPath), 0750, true);
|
||||
}
|
||||
|
||||
|
||||
// handle share-keys
|
||||
$localKeyPath = $view->getLocalFile($baseDir . 'share-keys/' . $params['oldpath']);
|
||||
$escapedPath = Helper::escapeGlobPattern($localKeyPath);
|
||||
$matches = glob($escapedPath . '*.shareKey');
|
||||
$matches = Helper::findShareKeys($pathOld, $oldShareKeyPath, $view);
|
||||
if (count($matches) === 0) {
|
||||
\OC_Log::write(
|
||||
'Encryption library', 'No share keys found for "' . $pathOld . '"',
|
||||
\OC_Log::WARN
|
||||
);
|
||||
}
|
||||
foreach ($matches as $src) {
|
||||
$dst = \OC\Files\Filesystem::normalizePath(str_replace($params['oldpath'], $params['newpath'], $src));
|
||||
|
||||
// create destination folder if not exists
|
||||
if (!file_exists(dirname($dst))) {
|
||||
mkdir(dirname($dst), 0750, true);
|
||||
}
|
||||
|
||||
rename($src, $dst);
|
||||
$dst = \OC\Files\Filesystem::normalizePath(str_replace($pathOld, $pathNew, $src));
|
||||
$view->$operation($src, $dst);
|
||||
}
|
||||
|
||||
} else {
|
||||
// handle share-keys folders
|
||||
$oldShareKeyfilePath = $baseDir . 'share-keys/' . $params['oldpath'];
|
||||
$newShareKeyfilePath = $baseDir . 'share-keys/' . $params['newpath'];
|
||||
|
||||
// create destination folder if not exists
|
||||
if (!$view->file_exists(dirname($newShareKeyfilePath))) {
|
||||
$view->mkdir(dirname($newShareKeyfilePath), 0750, true);
|
||||
if (!$view->file_exists(dirname($newShareKeyPath))) {
|
||||
mkdir($view->getLocalFile($newShareKeyPath), 0750, true);
|
||||
}
|
||||
|
||||
$view->rename($oldShareKeyfilePath, $newShareKeyfilePath);
|
||||
$view->$operation($oldShareKeyPath, $newShareKeyPath);
|
||||
}
|
||||
|
||||
// Rename keyfile so it isn't orphaned
|
||||
@@ -549,26 +663,17 @@ class Hooks {
|
||||
|
||||
// create destination folder if not exists
|
||||
if (!$view->file_exists(dirname($newKeyfilePath))) {
|
||||
$view->mkdir(dirname($newKeyfilePath), 0750, true);
|
||||
mkdir(dirname($view->getLocalFile($newKeyfilePath)), 0750, true);
|
||||
}
|
||||
|
||||
$view->rename($oldKeyfilePath, $newKeyfilePath);
|
||||
$view->$operation($oldKeyfilePath, $newKeyfilePath);
|
||||
}
|
||||
|
||||
// build the path to the file
|
||||
$newPath = '/' . $userId . '/files' . $params['newpath'];
|
||||
$newPathRelative = $params['newpath'];
|
||||
$newPath = '/' . $ownerNew . '/files' . $pathNew;
|
||||
|
||||
if ($util->fixFileSize($newPath)) {
|
||||
// get sharing app state
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
|
||||
// get users
|
||||
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $newPathRelative);
|
||||
|
||||
// update sharing-keys
|
||||
$util->setSharedFileKeyfiles($session, $usersSharing, $newPathRelative);
|
||||
}
|
||||
// update sharing-keys
|
||||
self::updateKeyfiles($params['newpath'], $type);
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
}
|
||||
@@ -600,4 +705,66 @@ class Hooks {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief if the file was really deleted we remove the encryption keys
|
||||
* @param array $params
|
||||
* @return boolean
|
||||
*/
|
||||
public static function postDelete($params) {
|
||||
|
||||
if (!isset(self::$deleteFiles[$params[\OC\Files\Filesystem::signal_param_path]])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$deletedFile = self::$deleteFiles[$params[\OC\Files\Filesystem::signal_param_path]];
|
||||
$path = $deletedFile['path'];
|
||||
$user = $deletedFile['uid'];
|
||||
|
||||
// we don't need to remember the file any longer
|
||||
unset(self::$deleteFiles[$params[\OC\Files\Filesystem::signal_param_path]]);
|
||||
|
||||
$view = new \OC\Files\View('/');
|
||||
|
||||
// return if the file still exists and wasn't deleted correctly
|
||||
if ($view->file_exists('/' . $user . '/files/' . $path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Disable encryption proxy to prevent recursive calls
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
// Delete keyfile & shareKey so it isn't orphaned
|
||||
if (!Keymanager::deleteFileKey($view, $path, $user)) {
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
'Keyfile or shareKey could not be deleted for file "' . $user.'/files/'.$path . '"', \OCP\Util::ERROR);
|
||||
}
|
||||
|
||||
Keymanager::delAllShareKeys($view, $user, $path);
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief remember the file which should be deleted and it's owner
|
||||
* @param array $params
|
||||
* @return boolean
|
||||
*/
|
||||
public static function preDelete($params) {
|
||||
$path = $params[\OC\Files\Filesystem::signal_param_path];
|
||||
|
||||
// skip this method if the trash bin is enabled or if we delete a file
|
||||
// outside of /data/user/files
|
||||
if (\OCP\App::isEnabled('files_trashbin')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$util = new Util(new \OC_FilesystemView('/'), \OCP\USER::getUser());
|
||||
list($owner, $ownerPath) = $util->getUidAndFilename($path);
|
||||
|
||||
self::$deleteFiles[$params[\OC\Files\Filesystem::signal_param_path]] = array(
|
||||
'uid' => $owner,
|
||||
'path' => $ownerPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,10 +17,14 @@ $(document).ready(function(){
|
||||
data: {user: user, password: password},
|
||||
async: false,
|
||||
success: function(response) {
|
||||
if (response.data.migrationCompleted === false) {
|
||||
if (response.data.migrationStatus === OC.Encryption.MIGRATION_OPEN) {
|
||||
var message = t('files_encryption', 'Initial encryption started... This can take some time. Please wait.');
|
||||
$('#messageText').text(message);
|
||||
$('#message').removeClass('hidden').addClass('update');
|
||||
} else if (response.data.migrationStatus === OC.Encryption.MIGRATION_IN_PROGRESS) {
|
||||
var message = t('files_encryption', 'Initial encryption running... Please try again later.');
|
||||
$('#messageText').text(message);
|
||||
$('#message').removeClass('hidden').addClass('update');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
12
apps/files_encryption/js/encryption.js
Normal file
12
apps/files_encryption/js/encryption.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2014
|
||||
* Bjoern Schiessle <schiessle@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
OC.Encryption={
|
||||
MIGRATION_OPEN:0,
|
||||
MIGRATION_COMPLETED:1,
|
||||
MIGRATION_IN_PROGRESS:-1,
|
||||
};
|
||||
@@ -29,6 +29,8 @@ namespace OCA\Encryption;
|
||||
*/
|
||||
class Helper {
|
||||
|
||||
private static $tmpFileMapping; // Map tmp files to files in data/user/files
|
||||
|
||||
/**
|
||||
* @brief register share related hooks
|
||||
*
|
||||
@@ -59,7 +61,12 @@ class Helper {
|
||||
*/
|
||||
public static function registerFilesystemHooks() {
|
||||
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRename');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Encryption\Hooks', 'preRename');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRenameOrCopy');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'copy', 'OCA\Encryption\Hooks', 'preCopy');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_copy', 'OCA\Encryption\Hooks', 'postRenameOrCopy');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_delete', 'OCA\Encryption\Hooks', 'postDelete');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Encryption\Hooks', 'preDelete');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,7 +281,7 @@ class Helper {
|
||||
$split = explode('/', $trimmed);
|
||||
|
||||
// it is not a file relative to data/user/files
|
||||
if (count($split) < 2 || $split[1] !== 'files') {
|
||||
if (count($split) < 2 || ($split[1] !== 'files' && $split[1] !== 'cache')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -360,9 +367,14 @@ class Helper {
|
||||
$post = 0;
|
||||
if(count($_POST) > 0) {
|
||||
$post = 1;
|
||||
}
|
||||
header('Location: ' . $location . '?p=' . $post . '&errorCode=' . $errorCode);
|
||||
exit();
|
||||
}
|
||||
|
||||
if(defined('PHPUNIT_RUN') and PHPUNIT_RUN) {
|
||||
throw new \Exception("Encryption error: $errorCode");
|
||||
}
|
||||
|
||||
header('Location: ' . $location . '?p=' . $post . '&errorCode=' . $errorCode);
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,7 +382,14 @@ class Helper {
|
||||
* @return bool true if requirements are met
|
||||
*/
|
||||
public static function checkRequirements() {
|
||||
return extension_loaded('openssl');
|
||||
$result = true;
|
||||
|
||||
//openssl extension needs to be loaded
|
||||
$result &= extension_loaded("openssl");
|
||||
// we need php >= 5.3.3
|
||||
$result &= version_compare(phpversion(), '5.3.3', '>=');
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -409,12 +428,66 @@ class Helper {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief glob uses different pattern than regular expressions, escape glob pattern only
|
||||
* @param unescaped path
|
||||
* @return escaped path
|
||||
* find all share keys for a given file
|
||||
*
|
||||
* @param string $filePath path to the file name relative to the user's files dir
|
||||
* for example "subdir/filename.txt"
|
||||
* @param string $shareKeyPath share key prefix path relative to the user's data dir
|
||||
* for example "user1/files_encryption/share-keys/subdir/filename.txt"
|
||||
* @param \OC\Files\View $rootView root view, relative to data/
|
||||
* @return array list of share key files, path relative to data/$user
|
||||
*/
|
||||
public static function escapeGlobPattern($path) {
|
||||
return preg_replace('/(\*|\?|\[)/', '[$1]', $path);
|
||||
public static function findShareKeys($filePath, $shareKeyPath, $rootView) {
|
||||
$result = array();
|
||||
|
||||
$user = \OCP\User::getUser();
|
||||
$util = new Util($rootView, $user);
|
||||
// get current sharing state
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
|
||||
// get users sharing this file
|
||||
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $filePath);
|
||||
|
||||
$pathinfo = pathinfo($shareKeyPath);
|
||||
|
||||
$baseDir = $pathinfo['dirname'] . '/';
|
||||
$fileName = $pathinfo['basename'];
|
||||
foreach ($usersSharing as $user) {
|
||||
$keyName = $fileName . '.' . $user . '.shareKey';
|
||||
if ($rootView->file_exists($baseDir . $keyName)) {
|
||||
$result[] = $baseDir . $keyName;
|
||||
} else {
|
||||
\OC_Log::write(
|
||||
'Encryption library',
|
||||
'No share key found for user "' . $user . '" for file "' . $pathOld . '"',
|
||||
\OC_Log::WARN
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief remember from which file the tmp file (getLocalFile() call) was created
|
||||
* @param string $tmpFile path of tmp file
|
||||
* @param string $originalFile path of the original file relative to data/
|
||||
*/
|
||||
public static function addTmpFileToMapper($tmpFile, $originalFile) {
|
||||
self::$tmpFileMapping[$tmpFile] = $originalFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief get the path of the original file
|
||||
* @param string $tmpFile path of the tmp file
|
||||
* @return mixed path of the original file or false
|
||||
*/
|
||||
public static function getPathFromTmpFile($tmpFile) {
|
||||
if (isset(self::$tmpFileMapping[$tmpFile])) {
|
||||
return self::$tmpFileMapping[$tmpFile];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,12 @@ class Keymanager {
|
||||
/**
|
||||
* @brief retrieve the ENCRYPTED private key from a user
|
||||
*
|
||||
* @param \OC_FilesystemView $view
|
||||
* @param \OC\Files\View $view
|
||||
* @param string $user
|
||||
* @return string private key or false (hopefully)
|
||||
* @note the key returned by this method must be decrypted before use
|
||||
*/
|
||||
public static function getPrivateKey(\OC_FilesystemView $view, $user) {
|
||||
public static function getPrivateKey($view, $user) {
|
||||
|
||||
$path = '/' . $user . '/' . 'files_encryption' . '/' . $user . '.private.key';
|
||||
$key = false;
|
||||
@@ -133,7 +133,7 @@ class Keymanager {
|
||||
$basePath = '/' . $owner . '/files_encryption/keyfiles';
|
||||
}
|
||||
|
||||
$targetPath = self::keySetPreparation($view, $filename, $basePath, $owner);
|
||||
$targetPath = self::keySetPreparation($view, $filename, $basePath);
|
||||
|
||||
if (!$view->is_dir($basePath . '/' . $targetPath)) {
|
||||
|
||||
@@ -214,15 +214,24 @@ class Keymanager {
|
||||
*
|
||||
* @param \OC_FilesystemView $view
|
||||
* @param string $path path of the file the key belongs to
|
||||
* @param string $userId the user to whom the file belongs
|
||||
* @return bool Outcome of unlink operation
|
||||
* @note $path must be relative to data/user/files. e.g. mydoc.txt NOT
|
||||
* /data/admin/files/mydoc.txt
|
||||
*/
|
||||
public static function deleteFileKey(\OC_FilesystemView $view, $path) {
|
||||
public static function deleteFileKey($view, $path, $userId=null) {
|
||||
|
||||
$trimmed = ltrim($path, '/');
|
||||
|
||||
$userId = Helper::getUser($path);
|
||||
if ($trimmed === '') {
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
'Can\'t delete file-key empty path given!', \OCP\Util::ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($userId === null) {
|
||||
$userId = Helper::getUser($path);
|
||||
}
|
||||
$util = new Util($view, $userId);
|
||||
|
||||
if($util->isSystemWideMountPoint($path)) {
|
||||
@@ -232,24 +241,23 @@ class Keymanager {
|
||||
}
|
||||
|
||||
$result = false;
|
||||
$fileExists = $view->file_exists('/' . $userId . '/files/' . $trimmed);
|
||||
|
||||
if ($view->is_dir($keyPath)) {
|
||||
|
||||
if ($view->is_dir($keyPath) && !$fileExists) {
|
||||
\OCP\Util::writeLog('files_encryption', 'deleteFileKey: delete file key: ' . $keyPath, \OCP\Util::DEBUG);
|
||||
$result = $view->unlink($keyPath);
|
||||
} elseif ($view->file_exists($keyPath . '.key') && !$fileExists) {
|
||||
\OCP\Util::writeLog('files_encryption', 'deleteFileKey: delete file key: ' . $keyPath, \OCP\Util::DEBUG);
|
||||
$result = $view->unlink($keyPath . '.key');
|
||||
|
||||
} else {
|
||||
if ($view->file_exists($keyPath . '.key')) {
|
||||
|
||||
$result = $view->unlink($keyPath . '.key');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!$result) {
|
||||
|
||||
if ($fileExists) {
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
'Could not delete keyfile; does not exist: "' . $keyPath, \OCP\Util::ERROR);
|
||||
|
||||
'Did not delete the file key, file still exists: ' . '/' . $userId . '/files/' . $trimmed, \OCP\Util::ERROR);
|
||||
} elseif (!$result) {
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
'Could not delete keyfile; does not exist: "' . $keyPath, \OCP\Util::ERROR);
|
||||
}
|
||||
|
||||
return $result;
|
||||
@@ -272,8 +280,9 @@ class Keymanager {
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
if (!$view->file_exists(''))
|
||||
if (!$view->file_exists('')) {
|
||||
$view->mkdir('');
|
||||
}
|
||||
|
||||
$result = $view->file_put_contents($user . '.private.key', $key);
|
||||
|
||||
@@ -331,7 +340,7 @@ class Keymanager {
|
||||
$basePath = '/' . $owner . '/files_encryption/share-keys';
|
||||
}
|
||||
|
||||
$shareKeyPath = self::keySetPreparation($view, $filename, $basePath, $owner);
|
||||
$shareKeyPath = self::keySetPreparation($view, $filename, $basePath);
|
||||
|
||||
$result = true;
|
||||
|
||||
@@ -402,7 +411,21 @@ class Keymanager {
|
||||
* @param string $userId owner of the file
|
||||
* @param string $filePath path to the file, relative to the owners file dir
|
||||
*/
|
||||
public static function delAllShareKeys(\OC_FilesystemView $view, $userId, $filePath) {
|
||||
public static function delAllShareKeys($view, $userId, $filePath) {
|
||||
|
||||
$filePath = ltrim($filePath, '/');
|
||||
|
||||
if ($view->file_exists('/' . $userId . '/files/' . $filePath)) {
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
'File still exists, stop deleting share keys!', \OCP\Util::ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($filePath === '') {
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
'Can\'t delete share-keys empty path given!', \OCP\Util::ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
$util = new util($view, $userId);
|
||||
|
||||
@@ -412,21 +435,28 @@ class Keymanager {
|
||||
$baseDir = $userId . '/files_encryption/share-keys/';
|
||||
}
|
||||
|
||||
$result = true;
|
||||
|
||||
if ($view->is_dir($userId . '/files/' . $filePath)) {
|
||||
$view->unlink($baseDir . $filePath);
|
||||
if ($view->is_dir($baseDir . $filePath)) {
|
||||
\OCP\Util::writeLog('files_encryption', 'delAllShareKeys: delete share keys: ' . $baseDir . $filePath, \OCP\Util::DEBUG);
|
||||
$result = $view->unlink($baseDir . $filePath);
|
||||
} else {
|
||||
$localKeyPath = $view->getLocalFile($baseDir . $filePath);
|
||||
$escapedPath = Helper::escapeGlobPattern($localKeyPath);
|
||||
$matches = glob($escapedPath . '*.shareKey');
|
||||
foreach ($matches as $ma) {
|
||||
$result = unlink($ma);
|
||||
if (!$result) {
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
'Keyfile or shareKey could not be deleted for file "' . $filePath . '"', \OCP\Util::ERROR);
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
$users = $util->getSharingUsersArray($sharingEnabled, $filePath);
|
||||
foreach($users as $user) {
|
||||
$keyName = $baseDir . $filePath . '.' . $user . '.shareKey';
|
||||
if ($view->file_exists($keyName)) {
|
||||
\OCP\Util::writeLog(
|
||||
'files_encryption',
|
||||
'dellAllShareKeys: delete share keys: "' . $keyName . '"',
|
||||
\OCP\Util::DEBUG
|
||||
);
|
||||
$result &= $view->unlink($keyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (bool)$result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,19 +481,23 @@ class Keymanager {
|
||||
|
||||
if ($view->is_dir($shareKeyPath)) {
|
||||
|
||||
$localPath = \OC\Files\Filesystem::normalizePath($view->getLocalFolder($shareKeyPath));
|
||||
self::recursiveDelShareKeys($localPath, $userIds);
|
||||
self::recursiveDelShareKeys($shareKeyPath, $userIds, $owner, $view);
|
||||
|
||||
} else {
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
|
||||
if (!$view->unlink($shareKeyPath . '.' . $userId . '.shareKey')) {
|
||||
if ($userId === $owner && $view->file_exists('/' . $owner . '/files/' . $filename)) {
|
||||
\OCP\Util::writeLog('files_encryption', 'Tried to delete owner key, but the file still exists!', \OCP\Util::FATAL);
|
||||
continue;
|
||||
}
|
||||
$result = $view->unlink($shareKeyPath . '.' . $userId . '.shareKey');
|
||||
\OCP\Util::writeLog('files_encryption', 'delShareKey: delete share key: ' . $shareKeyPath . '.' . $userId . '.shareKey' , \OCP\Util::DEBUG);
|
||||
if (!$result) {
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
'Could not delete shareKey; does not exist: "' . $shareKeyPath . '.' . $userId
|
||||
. '.shareKey"', \OCP\Util::ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,31 +509,47 @@ class Keymanager {
|
||||
*
|
||||
* @param string $dir directory
|
||||
* @param array $userIds user ids for which the share keys should be deleted
|
||||
* @param string $owner owner of the file
|
||||
* @param \OC\Files\View $view view relative to data/
|
||||
*/
|
||||
private static function recursiveDelShareKeys($dir, $userIds) {
|
||||
foreach ($userIds as $userId) {
|
||||
$extension = '.' . $userId . '.shareKey';
|
||||
$escapedDir = Helper::escapeGlobPattern($dir);
|
||||
$escapedExtension = Helper::escapeGlobPattern($extension);
|
||||
$matches = glob($escapedDir . '/*' . $escapedExtension);
|
||||
}
|
||||
/** @var $matches array */
|
||||
foreach ($matches as $ma) {
|
||||
if (!unlink($ma)) {
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
'Could not delete shareKey; does not exist: "' . $ma . '"', \OCP\Util::ERROR);
|
||||
private static function recursiveDelShareKeys($dir, $userIds, $owner, $view) {
|
||||
|
||||
$dirContent = $view->opendir($dir);
|
||||
$dirSlices = explode('/', ltrim($dir, '/'));
|
||||
$realFileDir = '/' . $owner . '/files/' . implode('/', array_slice($dirSlices, 3)) . '/';
|
||||
|
||||
if (is_resource($dirContent)) {
|
||||
while (($file = readdir($dirContent)) !== false) {
|
||||
if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
|
||||
if ($view->is_dir($dir . '/' . $file)) {
|
||||
self::recursiveDelShareKeys($dir . '/' . $file, $userIds, $owner, $view);
|
||||
} else {
|
||||
foreach ($userIds as $userId) {
|
||||
$fileNameFromShareKey = self::getFilenameFromShareKey($file, $userId);
|
||||
if (!$fileNameFromShareKey) {
|
||||
continue;
|
||||
}
|
||||
$realFile = $realFileDir . $fileNameFromShareKey;
|
||||
|
||||
if ($userId === $owner &&
|
||||
$view->file_exists($realFile)) {
|
||||
\OCP\Util::writeLog('files_encryption', 'original file still exists, keep owners share key!', \OCP\Util::ERROR);
|
||||
continue;
|
||||
}
|
||||
\OCP\Util::writeLog('files_encryption', 'recursiveDelShareKey: delete share key: ' . $file, \OCP\Util::DEBUG);
|
||||
$view->unlink($dir . '/' . $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$subdirs = $directories = glob($escapedDir . '/*', GLOB_ONLYDIR);
|
||||
foreach ($subdirs as $subdir) {
|
||||
self::recursiveDelShareKeys($subdir, $userIds);
|
||||
closedir($dirContent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Make preparations to vars and filesystem for saving a keyfile
|
||||
*/
|
||||
public static function keySetPreparation(\OC_FilesystemView $view, $path, $basePath, $userId) {
|
||||
public static function keySetPreparation(\OC_FilesystemView $view, $path, $basePath) {
|
||||
|
||||
$targetPath = ltrim($path, '/');
|
||||
|
||||
@@ -510,7 +560,7 @@ class Keymanager {
|
||||
isset($path_parts['dirname'])
|
||||
&& !$view->file_exists($basePath . '/' . $path_parts['dirname'])
|
||||
) {
|
||||
$sub_dirs = explode(DIRECTORY_SEPARATOR, $basePath . '/' . $path_parts['dirname']);
|
||||
$sub_dirs = explode('/', $basePath . '/' . $path_parts['dirname']);
|
||||
$dir = '';
|
||||
foreach ($sub_dirs as $sub_dir) {
|
||||
$dir .= '/' . $sub_dir;
|
||||
@@ -523,4 +573,22 @@ class Keymanager {
|
||||
return $targetPath;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief extract filename from share key name
|
||||
* @param string $shareKey (filename.userid.sharekey)
|
||||
* @return string|false filename or false
|
||||
*/
|
||||
protected static function getFilenameFromShareKey($shareKey, $userId) {
|
||||
$expectedSuffix = '.' . $userId . '.' . 'shareKey';
|
||||
$suffixLen = strlen($expectedSuffix);
|
||||
|
||||
$suffix = substr($shareKey, -$suffixLen);
|
||||
|
||||
if ($suffix !== $expectedSuffix) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return substr($shareKey, 0, -$suffixLen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace OCA\Encryption;
|
||||
class Proxy extends \OC_FileProxy {
|
||||
|
||||
private static $blackList = null; //mimetypes blacklisted from encryption
|
||||
private static $unencryptedSizes = array(); // remember unencrypted size
|
||||
private static $fopenMode = array(); // remember the fopen mode
|
||||
|
||||
/**
|
||||
* Check if a file requires encryption
|
||||
@@ -114,11 +116,20 @@ class Proxy extends \OC_FileProxy {
|
||||
// get encrypted content
|
||||
$data = $view->file_get_contents($tmpPath);
|
||||
|
||||
// store new unenecrypted size so that it can be updated
|
||||
// in the post proxy
|
||||
$tmpFileInfo = $view->getFileInfo($tmpPath);
|
||||
if ( isset($tmpFileInfo['size']) ) {
|
||||
self::$unencryptedSizes[\OC_Filesystem::normalizePath($path)] = $tmpFileInfo['size'];
|
||||
}
|
||||
|
||||
// remove our temp file
|
||||
$view->deleteAll('/' . \OCP\User::getUser() . '/cache/' . $cacheFolder);
|
||||
|
||||
// re-enable proxy - our work is done
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,6 +138,24 @@ class Proxy extends \OC_FileProxy {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief update file cache with the new unencrypted size after file was written
|
||||
* @param string $path
|
||||
* @param mixed $result
|
||||
* @return mixed
|
||||
*/
|
||||
public function postFile_put_contents($path, $result) {
|
||||
$normalizedPath = \OC_Filesystem::normalizePath($path);
|
||||
if ( isset(self::$unencryptedSizes[$normalizedPath]) ) {
|
||||
$view = new \OC_FilesystemView('/');
|
||||
$view->putFileInfo($normalizedPath,
|
||||
array('encrypted' => true, 'unencrypted_size' => self::$unencryptedSizes[$normalizedPath]));
|
||||
unset(self::$unencryptedSizes[$normalizedPath]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path Path of file from which has been read
|
||||
* @param string $data Data that has been read from file
|
||||
@@ -177,47 +206,6 @@ class Proxy extends \OC_FileProxy {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief When a file is deleted, remove its keyfile also
|
||||
*/
|
||||
public function preUnlink($path) {
|
||||
|
||||
// let the trashbin handle this
|
||||
if (\OCP\App::isEnabled('files_trashbin')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Disable encryption proxy to prevent recursive calls
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$view = new \OC_FilesystemView('/');
|
||||
|
||||
$userId = \OCP\USER::getUser();
|
||||
|
||||
$util = new Util($view, $userId);
|
||||
|
||||
// get relative path
|
||||
$relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path);
|
||||
|
||||
list($owner, $ownerPath) = $util->getUidAndFilename($relativePath);
|
||||
|
||||
// Delete keyfile & shareKey so it isn't orphaned
|
||||
if (!Keymanager::deleteFileKey($view, $ownerPath)) {
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
'Keyfile or shareKey could not be deleted for file "' . $ownerPath . '"', \OCP\Util::ERROR);
|
||||
}
|
||||
|
||||
Keymanager::delAllShareKeys($view, $owner, $ownerPath);
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
// If we don't return true then file delete will fail; better
|
||||
// to leave orphaned keyfiles than to disallow file deletion
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
* @return bool
|
||||
@@ -228,6 +216,16 @@ class Proxy extends \OC_FileProxy {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief remember initial fopen mode because sometimes it gets changed during the request
|
||||
* @param string $path path
|
||||
* @param string $mode type of access
|
||||
*/
|
||||
public function preFopen($path, $mode) {
|
||||
self::$fopenMode[$path] = $mode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
* @param $result
|
||||
@@ -246,8 +244,8 @@ class Proxy extends \OC_FileProxy {
|
||||
// split the path parts
|
||||
$pathParts = explode('/', $path);
|
||||
|
||||
// FIXME: handling for /userId/cache used by webdav for chunking. The cache chunks are NOT encrypted
|
||||
if (isset($pathParts[2]) && $pathParts[2] === 'cache') {
|
||||
// don't try to encrypt/decrypt cache chunks or files in the trash bin
|
||||
if (isset($pathParts[2]) && ($pathParts[2] === 'cache' || $pathParts[2] === 'files_trashbin')) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -255,7 +253,15 @@ class Proxy extends \OC_FileProxy {
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$meta = stream_get_meta_data($result);
|
||||
// if we remember the mode from the pre proxy we re-use it
|
||||
// oterwise we fall back to stream_get_meta_data()
|
||||
if (isset(self::$fopenMode[$path])) {
|
||||
$mode = self::$fopenMode[$path];
|
||||
unset(self::$fopenMode[$path]);
|
||||
} else {
|
||||
$meta = stream_get_meta_data($result);
|
||||
$mode = $meta['mode'];
|
||||
}
|
||||
|
||||
$view = new \OC_FilesystemView('');
|
||||
|
||||
@@ -273,14 +279,14 @@ class Proxy extends \OC_FileProxy {
|
||||
|
||||
// Open the file using the crypto stream wrapper
|
||||
// protocol and let it do the decryption work instead
|
||||
$result = fopen('crypt://' . $path, $meta['mode']);
|
||||
$result = fopen('crypt://' . $path, $mode);
|
||||
|
||||
} elseif (
|
||||
self::shouldEncrypt($path)
|
||||
and $meta ['mode'] !== 'r'
|
||||
and $meta['mode'] !== 'rb'
|
||||
self::shouldEncrypt($path)
|
||||
and $mode !== 'r'
|
||||
and $mode !== 'rb'
|
||||
) {
|
||||
$result = fopen('crypt://' . $path, $meta['mode']);
|
||||
$result = fopen('crypt://' . $path, $mode);
|
||||
}
|
||||
|
||||
// Re-enable the proxy
|
||||
@@ -305,7 +311,7 @@ class Proxy extends \OC_FileProxy {
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
// get file size
|
||||
$data['size'] = self::postFileSize($path, $data['size']);
|
||||
$data['size'] = self::postFileSize($path, $data['size'], $data);
|
||||
|
||||
// Re-enable the proxy
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
@@ -319,7 +325,7 @@ class Proxy extends \OC_FileProxy {
|
||||
* @param $size
|
||||
* @return bool
|
||||
*/
|
||||
public function postFileSize($path, $size) {
|
||||
public function postFileSize($path, $size, $fileInfo = null) {
|
||||
|
||||
$view = new \OC_FilesystemView('/');
|
||||
|
||||
@@ -335,6 +341,13 @@ class Proxy extends \OC_FileProxy {
|
||||
|
||||
// if path is a folder do nothing
|
||||
if ($view->is_dir($path)) {
|
||||
$proxyState = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
$fileInfo = $view->getFileInfo($path);
|
||||
\OC_FileProxy::$enabled = $proxyState;
|
||||
if (isset($fileInfo['unencrypted_size']) && $fileInfo['unencrypted_size'] > 0) {
|
||||
return $fileInfo['unencrypted_size'];
|
||||
}
|
||||
return $size;
|
||||
}
|
||||
|
||||
@@ -346,9 +359,8 @@ class Proxy extends \OC_FileProxy {
|
||||
return $size;
|
||||
}
|
||||
|
||||
$fileInfo = false;
|
||||
// get file info from database/cache if not .part file
|
||||
if (!Helper::isPartialFilePath($path)) {
|
||||
if (empty($fileInfo) && !Helper::isPartialFilePath($path)) {
|
||||
$proxyState = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
$fileInfo = $view->getFileInfo($path);
|
||||
@@ -356,7 +368,7 @@ class Proxy extends \OC_FileProxy {
|
||||
}
|
||||
|
||||
// if file is encrypted return real file size
|
||||
if (is_array($fileInfo) && $fileInfo['encrypted'] === true) {
|
||||
if (isset($fileInfo['encrypted']) && $fileInfo['encrypted'] === true) {
|
||||
// try to fix unencrypted file size if it doesn't look plausible
|
||||
if ((int)$fileInfo['size'] > 0 && (int)$fileInfo['unencrypted_size'] === 0 ) {
|
||||
$fixSize = $util->getFileSize($path);
|
||||
|
||||
@@ -132,6 +132,14 @@ class Session {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief remove encryption keys and init status from session
|
||||
*/
|
||||
public function closeSession() {
|
||||
\OC::$session->remove('encryptionInitialized');
|
||||
\OC::$session->remove('privateKey');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Gets status if we already tried to initialize the encryption app
|
||||
|
||||
@@ -64,6 +64,9 @@ class Stream {
|
||||
private $publicKey;
|
||||
private $encKeyfile;
|
||||
private $newFile; // helper var, we only need to write the keyfile for new files
|
||||
private $isLocalTmpFile = false; // do we operate on a local tmp file
|
||||
private $localTmpFile; // path of local tmp file
|
||||
|
||||
/**
|
||||
* @var \OC\Files\View
|
||||
*/
|
||||
@@ -91,13 +94,18 @@ class Stream {
|
||||
$this->rootView = new \OC_FilesystemView('/');
|
||||
}
|
||||
|
||||
|
||||
$this->session = new \OCA\Encryption\Session($this->rootView);
|
||||
|
||||
$this->privateKey = $this->session->getPrivateKey();
|
||||
|
||||
// rawPath is relative to the data directory
|
||||
$this->rawPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path));
|
||||
$normalizedPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path));
|
||||
if ($originalFile = Helper::getPathFromTmpFile($normalizedPath)) {
|
||||
$this->rawPath = $originalFile;
|
||||
$this->isLocalTmpFile = true;
|
||||
$this->localTmpFile = $normalizedPath;
|
||||
} else {
|
||||
$this->rawPath = $normalizedPath;
|
||||
}
|
||||
|
||||
$this->userId = Helper::getUser($this->rawPath);
|
||||
|
||||
@@ -141,10 +149,14 @@ class Stream {
|
||||
\OCA\Encryption\Helper::redirectToErrorPage($this->session);
|
||||
}
|
||||
|
||||
$this->size = $this->rootView->filesize($this->rawPath, $mode);
|
||||
$this->size = $this->rootView->filesize($this->rawPath);
|
||||
}
|
||||
|
||||
$this->handle = $this->rootView->fopen($this->rawPath, $mode);
|
||||
if ($this->isLocalTmpFile) {
|
||||
$this->handle = fopen($this->localTmpFile, $mode);
|
||||
} else {
|
||||
$this->handle = $this->rootView->fopen($this->rawPath, $mode);
|
||||
}
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
@@ -155,6 +167,9 @@ class Stream {
|
||||
} else {
|
||||
|
||||
$this->meta = stream_get_meta_data($this->handle);
|
||||
// sometimes fopen changes the mode, e.g. for a url "r" convert to "r+"
|
||||
// but we need to remember the original access type
|
||||
$this->meta['mode'] = $mode;
|
||||
|
||||
}
|
||||
|
||||
@@ -163,15 +178,26 @@ class Stream {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the current position of the file pointer
|
||||
* @return int position of the file pointer
|
||||
*/
|
||||
public function stream_tell() {
|
||||
return ftell($this->handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $offset
|
||||
* @param int $whence
|
||||
* @return bool true if fseek was successful, otherwise false
|
||||
*/
|
||||
public function stream_seek($offset, $whence = SEEK_SET) {
|
||||
|
||||
$this->flush();
|
||||
|
||||
fseek($this->handle, $offset, $whence);
|
||||
// this wrapper needs to return "true" for success.
|
||||
// the fseek call itself returns 0 on succeess
|
||||
return !fseek($this->handle, $offset, $whence);
|
||||
|
||||
}
|
||||
|
||||
@@ -477,7 +503,7 @@ class Stream {
|
||||
if ($this->privateKey === false) {
|
||||
|
||||
// cleanup
|
||||
if ($this->meta['mode'] !== 'r' && $this->meta['mode'] !== 'rb') {
|
||||
if ($this->meta['mode'] !== 'r' && $this->meta['mode'] !== 'rb' && !$this->isLocalTmpFile) {
|
||||
|
||||
// Disable encryption proxy to prevent recursive calls
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
@@ -498,6 +524,7 @@ class Stream {
|
||||
if (
|
||||
$this->meta['mode'] !== 'r' &&
|
||||
$this->meta['mode'] !== 'rb' &&
|
||||
$this->isLocalTmpFile === false &&
|
||||
$this->size > 0 &&
|
||||
$this->unencryptedSize > 0
|
||||
) {
|
||||
@@ -518,7 +545,7 @@ class Stream {
|
||||
$util = new Util($this->rootView, $this->userId);
|
||||
|
||||
// Get all users sharing the file includes current user
|
||||
$uniqueUserIds = $util->getSharingUsersArray($sharingEnabled, $this->relPath, $this->userId);
|
||||
$uniqueUserIds = $util->getSharingUsersArray($sharingEnabled, $this->relPath);
|
||||
$checkedUserIds = $util->filterShareReadyUsers($uniqueUserIds);
|
||||
|
||||
// Fetch public keys for all sharing users
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Sam Tuke, Frank Karlitschek
|
||||
* @author Sam Tuke, Frank Karlitschek, Bjoern Schiessle
|
||||
* @copyright 2012 Sam Tuke <samtuke@owncloud.com>,
|
||||
* Frank Karlitschek <frank@owncloud.org>
|
||||
* Frank Karlitschek <frank@owncloud.org>,
|
||||
* Bjoern Schiessle <schiessle@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
@@ -56,7 +57,7 @@ class Util {
|
||||
* @param $userId
|
||||
* @param bool $client
|
||||
*/
|
||||
public function __construct(\OC_FilesystemView $view, $userId, $client = false) {
|
||||
public function __construct($view, $userId, $client = false) {
|
||||
|
||||
$this->view = $view;
|
||||
$this->client = $client;
|
||||
@@ -101,15 +102,24 @@ class Util {
|
||||
or !$this->view->file_exists($this->publicKeyPath)
|
||||
or !$this->view->file_exists($this->privateKeyPath)
|
||||
) {
|
||||
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief check if the users private & public key exists
|
||||
* @return boolean
|
||||
*/
|
||||
public function userKeysExists() {
|
||||
if (
|
||||
$this->view->file_exists($this->privateKeyPath) &&
|
||||
$this->view->file_exists($this->publicKeyPath)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +133,6 @@ class Util {
|
||||
// Set directories to check / create
|
||||
$setUpDirs = array(
|
||||
$this->userDir,
|
||||
$this->userFilesDir,
|
||||
$this->publicKeyDir,
|
||||
$this->encryptionDir,
|
||||
$this->keyfilesPath,
|
||||
@@ -232,11 +241,9 @@ class Util {
|
||||
if (\OCP\DB::isError($result)) {
|
||||
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
|
||||
} else {
|
||||
if ($result->numRows() > 0) {
|
||||
$row = $result->fetchRow();
|
||||
if (isset($row['recovery_enabled'])) {
|
||||
$recoveryEnabled[] = $row['recovery_enabled'];
|
||||
}
|
||||
$row = $result->fetchRow();
|
||||
if ($row && isset($row['recovery_enabled'])) {
|
||||
$recoveryEnabled[] = $row['recovery_enabled'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +287,7 @@ class Util {
|
||||
$sql = 'UPDATE `*PREFIX*encryption` SET `recovery_enabled` = ? WHERE `uid` = ?';
|
||||
|
||||
$args = array(
|
||||
$enabled,
|
||||
$enabled ? '1' : '0',
|
||||
$this->userId
|
||||
);
|
||||
|
||||
@@ -308,7 +315,8 @@ class Util {
|
||||
$found = array(
|
||||
'plain' => array(),
|
||||
'encrypted' => array(),
|
||||
'legacy' => array()
|
||||
'legacy' => array(),
|
||||
'broken' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -319,10 +327,7 @@ class Util {
|
||||
if(is_resource($handle)) {
|
||||
while (false !== ($file = readdir($handle))) {
|
||||
|
||||
if (
|
||||
$file !== "."
|
||||
&& $file !== ".."
|
||||
) {
|
||||
if ($file !== "." && $file !== "..") {
|
||||
|
||||
$filePath = $directory . '/' . $this->view->getRelativePath('/' . $file);
|
||||
$relPath = \OCA\Encryption\Helper::stripUserFilesPath($filePath);
|
||||
@@ -349,15 +354,23 @@ class Util {
|
||||
// NOTE: This is inefficient;
|
||||
// scanning every file like this
|
||||
// will eat server resources :(
|
||||
if (
|
||||
Keymanager::getFileKey($this->view, $this, $relPath)
|
||||
&& $isEncryptedPath
|
||||
) {
|
||||
if ($isEncryptedPath) {
|
||||
|
||||
$found['encrypted'][] = array(
|
||||
'name' => $file,
|
||||
'path' => $filePath
|
||||
);
|
||||
$fileKey = Keymanager::getFileKey($this->view, $this, $relPath);
|
||||
$shareKey = Keymanager::getShareKey($this->view, $this->userId, $this, $relPath);
|
||||
// if file is encrypted but now file key is available, throw exception
|
||||
if ($fileKey === false || $shareKey === false) {
|
||||
\OCP\Util::writeLog('encryption library', 'No keys available to decrypt the file: ' . $filePath, \OCP\Util::ERROR);
|
||||
$found['broken'][] = array(
|
||||
'name' => $file,
|
||||
'path' => $filePath,
|
||||
);
|
||||
} else {
|
||||
$found['encrypted'][] = array(
|
||||
'name' => $file,
|
||||
'path' => $filePath,
|
||||
);
|
||||
}
|
||||
|
||||
// If the file uses old
|
||||
// encryption system
|
||||
@@ -455,20 +468,36 @@ class Util {
|
||||
*/
|
||||
public function isEncryptedPath($path) {
|
||||
|
||||
$relPath = Helper::getPathToRealFile($path);
|
||||
// Disable encryption proxy so data retrieved is in its
|
||||
// original form
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
if ($relPath === false) {
|
||||
$relPath = Helper::stripUserFilesPath($path);
|
||||
// we only need 24 byte from the last chunk
|
||||
$data = '';
|
||||
$handle = $this->view->fopen($path, 'r');
|
||||
if (is_resource($handle)) {
|
||||
// suppress fseek warining, we handle the case that fseek doesn't
|
||||
// work in the else branch
|
||||
if (@fseek($handle, -24, SEEK_END) === 0) {
|
||||
$data = fgets($handle);
|
||||
} else {
|
||||
// if fseek failed on the storage we create a local copy from the file
|
||||
// and read this one
|
||||
fclose($handle);
|
||||
$localFile = $this->view->getLocalFile($path);
|
||||
$handle = fopen($localFile, 'r');
|
||||
if (is_resource($handle) && fseek($handle, -24, SEEK_END) === 0) {
|
||||
$data = fgets($handle);
|
||||
}
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
$fileKey = Keymanager::getFileKey($this->view, $this, $relPath);
|
||||
|
||||
if ($fileKey === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// re-enable proxy
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
return Crypt::isCatfileContent($data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -514,7 +543,20 @@ class Util {
|
||||
$lastChunckPos = ($lastChunkNr * 8192);
|
||||
|
||||
// seek to end
|
||||
fseek($stream, $lastChunckPos);
|
||||
if (@fseek($stream, $lastChunckPos) === -1) {
|
||||
// storage doesn't support fseek, we need a local copy
|
||||
fclose($stream);
|
||||
$localFile = $this->view->getLocalFile($path);
|
||||
Helper::addTmpFileToMapper($localFile, $path);
|
||||
$stream = fopen('crypt://' . $localFile, "r");
|
||||
if (fseek($stream, $lastChunckPos) === -1) {
|
||||
// if fseek also fails on the local storage, than
|
||||
// there is nothing we can do
|
||||
fclose($stream);
|
||||
\OCP\Util::writeLog('Encryption library', 'couldn\'t determine size of "' . $path, \OCP\Util::ERROR);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// get the content of the last chunk
|
||||
$lastChunkContent = fread($stream, $lastChunkSize);
|
||||
@@ -761,7 +803,7 @@ class Util {
|
||||
\OC\Files\Filesystem::putFileInfo($relPath, array(
|
||||
'encrypted' => false,
|
||||
'size' => $size,
|
||||
'unencrypted_size' => $size,
|
||||
'unencrypted_size' => 0,
|
||||
'etag' => $fileInfo['etag']
|
||||
));
|
||||
|
||||
@@ -777,6 +819,12 @@ class Util {
|
||||
$successful = false;
|
||||
}
|
||||
|
||||
// if there are broken encrypted files than the complete decryption
|
||||
// was not successful
|
||||
if (!empty($found['broken'])) {
|
||||
$successful = false;
|
||||
}
|
||||
|
||||
if ($successful) {
|
||||
$this->view->deleteAll($this->keyfilesPath);
|
||||
$this->view->deleteAll($this->shareKeysPath);
|
||||
@@ -831,32 +879,35 @@ class Util {
|
||||
// Open enc file handle for binary writing, with same filename as original plain file
|
||||
$encHandle = fopen('crypt://' . $rawPath . '.part', 'wb');
|
||||
|
||||
// Move plain file to a temporary location
|
||||
$size = stream_copy_to_stream($plainHandle, $encHandle);
|
||||
if (is_resource($encHandle)) {
|
||||
// Move plain file to a temporary location
|
||||
$size = stream_copy_to_stream($plainHandle, $encHandle);
|
||||
|
||||
fclose($encHandle);
|
||||
fclose($plainHandle);
|
||||
fclose($encHandle);
|
||||
fclose($plainHandle);
|
||||
|
||||
$fakeRoot = $this->view->getRoot();
|
||||
$this->view->chroot('/' . $this->userId . '/files');
|
||||
$fakeRoot = $this->view->getRoot();
|
||||
$this->view->chroot('/' . $this->userId . '/files');
|
||||
|
||||
$this->view->rename($relPath . '.part', $relPath);
|
||||
$this->view->rename($relPath . '.part', $relPath);
|
||||
|
||||
// set timestamp
|
||||
$this->view->touch($relPath, $timestamp);
|
||||
// set timestamp
|
||||
$this->view->touch($relPath, $timestamp);
|
||||
|
||||
$this->view->chroot($fakeRoot);
|
||||
$encSize = $this->view->filesize($relPath);
|
||||
|
||||
// Add the file to the cache
|
||||
\OC\Files\Filesystem::putFileInfo($relPath, array(
|
||||
'encrypted' => true,
|
||||
'size' => $size,
|
||||
'unencrypted_size' => $size,
|
||||
'etag' => $fileInfo['etag']
|
||||
));
|
||||
$this->view->chroot($fakeRoot);
|
||||
|
||||
$encryptedFiles[] = $relPath;
|
||||
// Add the file to the cache
|
||||
\OC\Files\Filesystem::putFileInfo($relPath, array(
|
||||
'encrypted' => true,
|
||||
'size' => $encSize,
|
||||
'unencrypted_size' => $size,
|
||||
'etag' => $fileInfo['etag']
|
||||
));
|
||||
|
||||
$encryptedFiles[] = $relPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt legacy encrypted files
|
||||
@@ -973,8 +1024,8 @@ class Util {
|
||||
if (\OCP\DB::isError($result)) {
|
||||
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
|
||||
} else {
|
||||
if ($result->numRows() > 0) {
|
||||
$row = $result->fetchRow();
|
||||
$row = $result->fetchRow();
|
||||
if ($row) {
|
||||
$path = substr($row['path'], strlen('files'));
|
||||
}
|
||||
}
|
||||
@@ -1090,6 +1141,10 @@ class Util {
|
||||
// Re-enc keyfile to (additional) sharekeys
|
||||
$multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys);
|
||||
|
||||
if ($multiEncKey === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save the recrypted key to it's owner's keyfiles directory
|
||||
// Save new sharekeys to all necessary user directory
|
||||
if (
|
||||
@@ -1113,8 +1168,10 @@ class Util {
|
||||
/**
|
||||
* @brief Find, sanitise and format users sharing a file
|
||||
* @note This wraps other methods into a portable bundle
|
||||
* @param boolean $sharingEnabled
|
||||
* @param string $filePath path relativ to current users files folder
|
||||
*/
|
||||
public function getSharingUsersArray($sharingEnabled, $filePath, $currentUserId = false) {
|
||||
public function getSharingUsersArray($sharingEnabled, $filePath) {
|
||||
|
||||
// Check if key recovery is enabled
|
||||
if (
|
||||
@@ -1131,12 +1188,14 @@ class Util {
|
||||
|
||||
$ownerPath = \OCA\Encryption\Helper::stripPartialFileExtension($ownerPath);
|
||||
|
||||
$userIds = array();
|
||||
// always add owner to the list of users with access to the file
|
||||
$userIds = array($owner);
|
||||
|
||||
if ($sharingEnabled) {
|
||||
|
||||
// Find out who, if anyone, is sharing the file
|
||||
$result = \OCP\Share::getUsersSharingFile($ownerPath, $owner, true);
|
||||
$userIds = $result['users'];
|
||||
$result = \OCP\Share::getUsersSharingFile($ownerPath, $owner);
|
||||
$userIds = \array_merge($userIds, $result['users']);
|
||||
if ($result['public']) {
|
||||
$userIds[] = $this->publicShareKeyId;
|
||||
}
|
||||
@@ -1152,17 +1211,12 @@ class Util {
|
||||
$userIds[] = $recoveryKeyId;
|
||||
}
|
||||
|
||||
// add current user if given
|
||||
if ($currentUserId !== false) {
|
||||
$userIds[] = $currentUserId;
|
||||
}
|
||||
|
||||
// check if it is a group mount
|
||||
if (\OCP\App::isEnabled("files_external")) {
|
||||
$mount = \OC_Mount_Config::getSystemMountPoints();
|
||||
foreach ($mount as $mountPoint => $data) {
|
||||
if ($mountPoint == substr($ownerPath, 1, strlen($mountPoint))) {
|
||||
$userIds = array_merge($userIds, $this->getUserWithAccessToMountPoint($data['applicable']['users'], $data['applicable']['groups']));
|
||||
$mounts = \OC_Mount_Config::getSystemMountPoints();
|
||||
foreach ($mounts as $mount) {
|
||||
if ($mount['mountpoint'] == substr($ownerPath, 1, strlen($mount['mountpoint']))) {
|
||||
$userIds = array_merge($userIds, $this->getUserWithAccessToMountPoint($mount['applicable']['users'], $mount['applicable']['groups']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1188,27 +1242,49 @@ class Util {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief set migration status
|
||||
* @param int $status
|
||||
* @return boolean
|
||||
*/
|
||||
private function setMigrationStatus($status) {
|
||||
|
||||
$sql = 'UPDATE `*PREFIX*encryption` SET `migration_status` = ? WHERE `uid` = ?';
|
||||
$args = array($status, $this->userId);
|
||||
$query = \OCP\DB::prepare($sql);
|
||||
$manipulatedRows = $query->execute($args);
|
||||
|
||||
if ($manipulatedRows === 1) {
|
||||
$result = true;
|
||||
\OCP\Util::writeLog('Encryption library', "Migration status set to " . self::MIGRATION_OPEN, \OCP\Util::INFO);
|
||||
} else {
|
||||
$result = false;
|
||||
\OCP\Util::writeLog('Encryption library', "Could not set migration status to " . self::MIGRATION_OPEN, \OCP\Util::WARN);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief start migration mode to initially encrypt users data
|
||||
* @return boolean
|
||||
*/
|
||||
public function beginMigration() {
|
||||
|
||||
$return = false;
|
||||
$result = $this->setMigrationStatus(self::MIGRATION_IN_PROGRESS);
|
||||
|
||||
$sql = 'UPDATE `*PREFIX*encryption` SET `migration_status` = ? WHERE `uid` = ? and `migration_status` = ?';
|
||||
$args = array(self::MIGRATION_IN_PROGRESS, $this->userId, self::MIGRATION_OPEN);
|
||||
$query = \OCP\DB::prepare($sql);
|
||||
$manipulatedRows = $query->execute($args);
|
||||
|
||||
if ($manipulatedRows === 1) {
|
||||
$return = true;
|
||||
if ($result) {
|
||||
\OCP\Util::writeLog('Encryption library', "Start migration to encryption mode for " . $this->userId, \OCP\Util::INFO);
|
||||
} else {
|
||||
\OCP\Util::writeLog('Encryption library', "Could not activate migration mode for " . $this->userId . ". Probably another process already started the initial encryption", \OCP\Util::WARN);
|
||||
}
|
||||
|
||||
return $return;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function resetMigrationStatus() {
|
||||
return $this->setMigrationStatus(self::MIGRATION_OPEN);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1216,22 +1292,15 @@ class Util {
|
||||
* @return boolean
|
||||
*/
|
||||
public function finishMigration() {
|
||||
$result = $this->setMigrationStatus(self::MIGRATION_COMPLETED);
|
||||
|
||||
$return = false;
|
||||
|
||||
$sql = 'UPDATE `*PREFIX*encryption` SET `migration_status` = ? WHERE `uid` = ? and `migration_status` = ?';
|
||||
$args = array(self::MIGRATION_COMPLETED, $this->userId, self::MIGRATION_IN_PROGRESS);
|
||||
$query = \OCP\DB::prepare($sql);
|
||||
$manipulatedRows = $query->execute($args);
|
||||
|
||||
if ($manipulatedRows === 1) {
|
||||
$return = true;
|
||||
if ($result) {
|
||||
\OCP\Util::writeLog('Encryption library', "Finish migration successfully for " . $this->userId, \OCP\Util::INFO);
|
||||
} else {
|
||||
\OCP\Util::writeLog('Encryption library', "Could not deactivate migration mode for " . $this->userId, \OCP\Util::WARN);
|
||||
}
|
||||
|
||||
return $return;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1254,11 +1323,9 @@ class Util {
|
||||
if (\OCP\DB::isError($result)) {
|
||||
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
|
||||
} else {
|
||||
if ($result->numRows() > 0) {
|
||||
$row = $result->fetchRow();
|
||||
if (isset($row['migration_status'])) {
|
||||
$migrationStatus[] = $row['migration_status'];
|
||||
}
|
||||
$row = $result->fetchRow();
|
||||
if ($row && isset($row['migration_status'])) {
|
||||
$migrationStatus[] = $row['migration_status'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1366,59 +1433,32 @@ class Util {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief go recursively through a dir and collect all files and sub files.
|
||||
* @param string $dir relative to the users files folder
|
||||
* @return array with list of files relative to the users files folder
|
||||
*/
|
||||
public function getAllFiles($dir) {
|
||||
|
||||
$result = array();
|
||||
$dirList = array($dir);
|
||||
|
||||
$content = $this->view->getDirectoryContent(\OC\Files\Filesystem::normalizePath(
|
||||
$this->userFilesDir . '/' . $dir));
|
||||
|
||||
// handling for re shared folders
|
||||
$pathSplit = explode('/', $dir);
|
||||
|
||||
foreach ($content as $c) {
|
||||
|
||||
$sharedPart = $pathSplit[sizeof($pathSplit) - 1];
|
||||
$targetPathSplit = array_reverse(explode('/', $c['path']));
|
||||
|
||||
$path = '';
|
||||
|
||||
// rebuild path
|
||||
foreach ($targetPathSplit as $pathPart) {
|
||||
|
||||
if ($pathPart !== $sharedPart) {
|
||||
|
||||
$path = '/' . $pathPart . $path;
|
||||
while ($dirList) {
|
||||
$dir = array_pop($dirList);
|
||||
$content = $this->view->getDirectoryContent(\OC\Files\Filesystem::normalizePath(
|
||||
$this->userFilesDir . '/' . $dir));
|
||||
|
||||
foreach ($content as $c) {
|
||||
$usersPath = isset($c['usersPath']) ? $c['usersPath'] : $c['path'];
|
||||
if ($c['type'] === 'dir') {
|
||||
$dirList[] = substr($usersPath, strlen("files"));
|
||||
} else {
|
||||
|
||||
break;
|
||||
|
||||
$result[] = substr($usersPath, strlen("files"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$path = $dir . $path;
|
||||
|
||||
if ($c['type'] === 'dir') {
|
||||
|
||||
$result = array_merge($result, $this->getAllFiles($path));
|
||||
|
||||
} else {
|
||||
|
||||
$result[] = $path;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1438,9 +1478,7 @@ class Util {
|
||||
if (\OCP\DB::isError($result)) {
|
||||
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
|
||||
} else {
|
||||
if ($result->numRows() > 0) {
|
||||
$row = $result->fetchRow();
|
||||
}
|
||||
$row = $result->fetchRow();
|
||||
}
|
||||
|
||||
return $row;
|
||||
@@ -1464,9 +1502,7 @@ class Util {
|
||||
if (\OCP\DB::isError($result)) {
|
||||
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
|
||||
} else {
|
||||
if ($result->numRows() > 0) {
|
||||
$row = $result->fetchRow();
|
||||
}
|
||||
$row = $result->fetchRow();
|
||||
}
|
||||
|
||||
return $row;
|
||||
@@ -1485,18 +1521,16 @@ class Util {
|
||||
|
||||
$result = $query->execute(array($id));
|
||||
|
||||
$source = array();
|
||||
$source = null;
|
||||
if (\OCP\DB::isError($result)) {
|
||||
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
|
||||
} else {
|
||||
if ($result->numRows() > 0) {
|
||||
$source = $result->fetchRow();
|
||||
}
|
||||
$source = $result->fetchRow();
|
||||
}
|
||||
|
||||
$fileOwner = false;
|
||||
|
||||
if (isset($source['parent'])) {
|
||||
if ($source && isset($source['parent'])) {
|
||||
|
||||
$parent = $source['parent'];
|
||||
|
||||
@@ -1506,16 +1540,14 @@ class Util {
|
||||
|
||||
$result = $query->execute(array($parent));
|
||||
|
||||
$item = array();
|
||||
$item = null;
|
||||
if (\OCP\DB::isError($result)) {
|
||||
\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
|
||||
} else {
|
||||
if ($result->numRows() > 0) {
|
||||
$item = $result->fetchRow();
|
||||
}
|
||||
$item = $result->fetchRow();
|
||||
}
|
||||
|
||||
if (isset($item['parent'])) {
|
||||
if ($item && isset($item['parent'])) {
|
||||
|
||||
$parent = $item['parent'];
|
||||
|
||||
@@ -1738,14 +1770,61 @@ class Util {
|
||||
/**
|
||||
* @brief check if the file is stored on a system wide mount point
|
||||
* @param $path relative to /data/user with leading '/'
|
||||
* create a backup of all keys from the user
|
||||
*
|
||||
* @param string $purpose (optional) define the purpose of the backup, will be part of the backup folder
|
||||
*/
|
||||
public function backupAllKeys($purpose = '') {
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$backupDir = $this->encryptionDir . '/backup.';
|
||||
$backupDir .= ($purpose === '') ? date("Y-m-d_H-i-s") . '/' : $purpose . '.' . date("Y-m-d_H-i-s") . '/';
|
||||
$this->view->mkdir($backupDir);
|
||||
$this->copyRecursive($this->shareKeysPath, $backupDir . 'share-keys/');
|
||||
$this->copyRecursive($this->keyfilesPath, $backupDir . 'keyfiles/');
|
||||
$this->view->copy($this->privateKeyPath, $backupDir . $this->userId . '.private.key');
|
||||
$this->view->copy($this->publicKeyPath, $backupDir . $this->userId . '.public.key');
|
||||
|
||||
\OC_FileProxy::$enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* helper method to copy a folder recursively, only needed in OC6.
|
||||
* OC7 filesystem and newer can copy folder structures
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
*/
|
||||
private function copyRecursive($source, $target) {
|
||||
if ($this->view->is_dir($source)) {
|
||||
$this->view->mkdir($target);
|
||||
$dir = $this->view->opendir($source);
|
||||
while ($file = readdir($dir)) {
|
||||
if(!\OC\Files\Filesystem::isIgnoredDir($file)) {
|
||||
$this->copyRecursive($source . '/' . $file, $target . '/' . $file);
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
} else {
|
||||
$this->view->copy($source, $target);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* check if the file is stored on a system wide mount point
|
||||
* @param string $path relative to /data/user with leading '/'
|
||||
* @return boolean
|
||||
*/
|
||||
public function isSystemWideMountPoint($path) {
|
||||
$normalizedPath = ltrim($path, '/');
|
||||
if (\OCP\App::isEnabled("files_external")) {
|
||||
$mount = \OC_Mount_Config::getSystemMountPoints();
|
||||
foreach ($mount as $mountPoint => $data) {
|
||||
if ($mountPoint == substr($path, 1, strlen($mountPoint))) {
|
||||
return true;
|
||||
$mounts = \OC_Mount_Config::getSystemMountPoints();
|
||||
foreach ($mounts as $mount) {
|
||||
if ($mount['mountpoint'] == substr($normalizedPath, 0, strlen($mount['mountpoint']))) {
|
||||
if ($this->isMountPointApplicableToUser($mount)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1753,7 +1832,30 @@ class Util {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief decrypt private key and add it to the current session
|
||||
* check if mount point is applicable to user
|
||||
*
|
||||
* @param array $mount contains $mount['applicable']['users'], $mount['applicable']['groups']
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isMountPointApplicableToUser($mount) {
|
||||
$uid = \OCP\User::getUser();
|
||||
$acceptedUids = array('all', $uid);
|
||||
// check if mount point is applicable for the user
|
||||
$intersection = array_intersect($acceptedUids, $mount['applicable']['users']);
|
||||
if (!empty($intersection)) {
|
||||
return true;
|
||||
}
|
||||
// check if mount point is applicable for group where the user is a member
|
||||
foreach ($mount['applicable']['groups'] as $gid) {
|
||||
if (\OC_Group::inGroup($uid, $gid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* decrypt private key and add it to the current session
|
||||
* @param array $params with 'uid' and 'password'
|
||||
* @return mixed session or false
|
||||
*/
|
||||
@@ -1780,4 +1882,12 @@ class Util {
|
||||
return $session;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief remove encryption related keys from the session
|
||||
*/
|
||||
public function closeEncryptionSession() {
|
||||
$session = new \OCA\Encryption\Session($this->view);
|
||||
$session->closeSession();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<fieldset class="personalblock">
|
||||
<h2><?php p( $l->t( 'Encryption' ) ); ?></h2>
|
||||
|
||||
<?php if ( ! $_["privateKeySet"] && $_["initialized"] ): ?>
|
||||
<?php if ( $_["initialized"] === '1' ): ?>
|
||||
<p>
|
||||
<a name="changePKPasswd" />
|
||||
<label for="changePrivateKeyPasswd">
|
||||
|
||||
@@ -46,13 +46,12 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
\OC_User::clearBackends();
|
||||
\OC_User::useBackend('database');
|
||||
|
||||
// Filesystem related hooks
|
||||
// clear hooks and register encryption hooks
|
||||
\OC_Hook::clear();
|
||||
\OCA\Encryption\Helper::registerFilesystemHooks();
|
||||
|
||||
// Filesystem related hooks
|
||||
\OCA\Encryption\Helper::registerUserHooks();
|
||||
|
||||
// clear and register hooks
|
||||
// clear and register proxies
|
||||
\OC_FileProxy::clearProxies();
|
||||
\OC_FileProxy::register(new OCA\Encryption\Proxy());
|
||||
|
||||
@@ -95,6 +94,8 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
} else {
|
||||
OC_App::disable('files_trashbin');
|
||||
}
|
||||
|
||||
$this->assertTrue(\OC_FileProxy::$enabled);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass() {
|
||||
@@ -155,7 +156,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
*/
|
||||
function testSymmetricStreamEncryptShortFileContent() {
|
||||
|
||||
$filename = 'tmp-' . time() . '.test';
|
||||
$filename = 'tmp-' . uniqid() . '.test';
|
||||
|
||||
$util = new Encryption\Util(new \OC_FilesystemView(), $this->userId);
|
||||
|
||||
@@ -214,7 +215,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
function testSymmetricStreamEncryptLongFileContent() {
|
||||
|
||||
// Generate a a random filename
|
||||
$filename = 'tmp-' . time() . '.test';
|
||||
$filename = 'tmp-' . uniqid() . '.test';
|
||||
|
||||
$util = new Encryption\Util(new \OC_FilesystemView(), $this->userId);
|
||||
|
||||
@@ -297,7 +298,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
*/
|
||||
function testSymmetricStreamDecryptShortFileContent() {
|
||||
|
||||
$filename = 'tmp-' . time();
|
||||
$filename = 'tmp-' . uniqid();
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = file_put_contents('crypt:///'. $this->userId . '/files/' . $filename, $this->dataShort);
|
||||
@@ -327,7 +328,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
*/
|
||||
function testSymmetricStreamDecryptLongFileContent() {
|
||||
|
||||
$filename = 'tmp-' . time();
|
||||
$filename = 'tmp-' . uniqid();
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong);
|
||||
@@ -418,7 +419,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
*/
|
||||
function testRenameFile() {
|
||||
|
||||
$filename = 'tmp-' . time();
|
||||
$filename = 'tmp-' . uniqid();
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong);
|
||||
@@ -431,7 +432,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$this->assertEquals($this->dataLong, $decrypt);
|
||||
|
||||
$newFilename = 'tmp-new-' . time();
|
||||
$newFilename = 'tmp-new-' . uniqid();
|
||||
$view = new \OC\Files\View('/' . $this->userId . '/files');
|
||||
$view->rename($filename, $newFilename);
|
||||
|
||||
@@ -449,7 +450,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
*/
|
||||
function testMoveFileIntoFolder() {
|
||||
|
||||
$filename = 'tmp-' . time();
|
||||
$filename = 'tmp-' . uniqid();
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong);
|
||||
@@ -462,8 +463,8 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$this->assertEquals($this->dataLong, $decrypt);
|
||||
|
||||
$newFolder = '/newfolder' . time();
|
||||
$newFilename = 'tmp-new-' . time();
|
||||
$newFolder = '/newfolder' . uniqid();
|
||||
$newFilename = 'tmp-new-' . uniqid();
|
||||
$view = new \OC\Files\View('/' . $this->userId . '/files');
|
||||
$view->mkdir($newFolder);
|
||||
$view->rename($filename, $newFolder . '/' . $newFilename);
|
||||
@@ -484,8 +485,8 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$view = new \OC\Files\View('/' . $this->userId . '/files');
|
||||
|
||||
$filename = '/tmp-' . time();
|
||||
$folder = '/folder' . time();
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$folder = '/folder' . uniqid();
|
||||
|
||||
$view->mkdir($folder);
|
||||
|
||||
@@ -500,7 +501,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$this->assertEquals($this->dataLong, $decrypt);
|
||||
|
||||
$newFolder = '/newfolder/subfolder' . time();
|
||||
$newFolder = '/newfolder/subfolder' . uniqid();
|
||||
$view->mkdir('/newfolder');
|
||||
|
||||
$view->rename($folder, $newFolder);
|
||||
@@ -519,7 +520,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
* @medium
|
||||
*/
|
||||
function testChangePassphrase() {
|
||||
$filename = 'tmp-' . time();
|
||||
$filename = 'tmp-' . uniqid();
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong);
|
||||
@@ -557,7 +558,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
*/
|
||||
function testViewFilePutAndGetContents() {
|
||||
|
||||
$filename = '/tmp-' . time();
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$view = new \OC\Files\View('/' . $this->userId . '/files');
|
||||
|
||||
// Save short data as encrypted file using stream wrapper
|
||||
@@ -590,7 +591,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
* @large
|
||||
*/
|
||||
function testTouchExistingFile() {
|
||||
$filename = '/tmp-' . time();
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$view = new \OC\Files\View('/' . $this->userId . '/files');
|
||||
|
||||
// Save short data as encrypted file using stream wrapper
|
||||
@@ -614,7 +615,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
* @medium
|
||||
*/
|
||||
function testTouchFile() {
|
||||
$filename = '/tmp-' . time();
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$view = new \OC\Files\View('/' . $this->userId . '/files');
|
||||
|
||||
$view->touch($filename);
|
||||
@@ -638,7 +639,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
* @medium
|
||||
*/
|
||||
function testFopenFile() {
|
||||
$filename = '/tmp-' . time();
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$view = new \OC\Files\View('/' . $this->userId . '/files');
|
||||
|
||||
// Save short data as encrypted file using stream wrapper
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
|
||||
require_once __DIR__ . '/../lib/helper.php';
|
||||
require_once __DIR__ . '/util.php';
|
||||
|
||||
use OCA\Encryption;
|
||||
|
||||
@@ -16,6 +17,18 @@ use OCA\Encryption;
|
||||
*/
|
||||
class Test_Encryption_Helper extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
const TEST_ENCRYPTION_HELPER_USER1 = "test-helper-user1";
|
||||
|
||||
public static function setUpBeforeClass() {
|
||||
// create test user
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER1, true);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass() {
|
||||
// cleanup test user
|
||||
\OC_User::deleteUser(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
@@ -64,4 +77,84 @@ class Test_Encryption_Helper extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertEquals($relativePath, Encryption\Helper::getPathToRealFile($cachePath));
|
||||
}
|
||||
|
||||
function testGetUser() {
|
||||
|
||||
$path1 = "/" . self::TEST_ENCRYPTION_HELPER_USER1 . "/files/foo/bar.txt";
|
||||
$path2 = "/" . self::TEST_ENCRYPTION_HELPER_USER1 . "/cache/foo/bar.txt";
|
||||
$path3 = "/" . self::TEST_ENCRYPTION_HELPER_USER1 . "/thumbnails/foo";
|
||||
$path4 ="/" . "/" . self::TEST_ENCRYPTION_HELPER_USER1;
|
||||
|
||||
// if we are logged-in every path should return the currently logged-in user
|
||||
$this->assertEquals(self::TEST_ENCRYPTION_HELPER_USER1, Encryption\Helper::getUser($path3));
|
||||
|
||||
// now log out
|
||||
\Test_Encryption_Util::logoutHelper();
|
||||
|
||||
// now we should only get the user from /user/files and user/cache paths
|
||||
$this->assertEquals(self::TEST_ENCRYPTION_HELPER_USER1, Encryption\Helper::getUser($path1));
|
||||
$this->assertEquals(self::TEST_ENCRYPTION_HELPER_USER1, Encryption\Helper::getUser($path2));
|
||||
|
||||
$this->assertFalse(Encryption\Helper::getUser($path3));
|
||||
$this->assertFalse(Encryption\Helper::getUser($path4));
|
||||
|
||||
// Log-in again
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER1);
|
||||
}
|
||||
|
||||
function userNamesProvider() {
|
||||
return array(
|
||||
array('testuser' . uniqid()),
|
||||
array('user.name.with.dots'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether share keys can be found
|
||||
*
|
||||
* @dataProvider userNamesProvider
|
||||
*/
|
||||
function testFindShareKeys($userName) {
|
||||
// note: not using dataProvider as we want to make
|
||||
// sure that the correct keys are match and not any
|
||||
// other ones that might happen to have similar names
|
||||
\Test_Encryption_Util::setupHooks();
|
||||
\Test_Encryption_Util::loginHelper($userName, true);
|
||||
$testDir = 'testFindShareKeys' . uniqid() . '/';
|
||||
$baseDir = $userName . '/files/' . $testDir;
|
||||
$fileList = array(
|
||||
't est.txt',
|
||||
't est_.txt',
|
||||
't est.doc.txt',
|
||||
't est(.*).txt', // make sure the regexp is escaped
|
||||
'multiple.dots.can.happen.too.txt',
|
||||
't est.' . $userName . '.txt',
|
||||
't est_.' . $userName . '.shareKey.txt',
|
||||
'who would upload their.shareKey',
|
||||
'user ones file.txt',
|
||||
'user ones file.txt.backup',
|
||||
'.t est.txt'
|
||||
);
|
||||
|
||||
$rootView = new \OC\Files\View('/');
|
||||
$rootView->mkdir($baseDir);
|
||||
foreach ($fileList as $fileName) {
|
||||
$rootView->file_put_contents($baseDir . $fileName, 'dummy');
|
||||
}
|
||||
|
||||
$shareKeysDir = $userName . '/files_encryption/share-keys/' . $testDir;
|
||||
foreach ($fileList as $fileName) {
|
||||
// make sure that every file only gets its correct respective keys
|
||||
$result = Encryption\Helper::findShareKeys($baseDir . $fileName, $shareKeysDir . $fileName, $rootView);
|
||||
$this->assertEquals(
|
||||
array($shareKeysDir . $fileName . '.' . $userName . '.shareKey'),
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
// clean up
|
||||
$rootView->unlink($baseDir);
|
||||
\Test_Encryption_Util::logoutHelper();
|
||||
\OC_User::deleteUser($userName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
462
apps/files_encryption/tests/hooks.php
Normal file
462
apps/files_encryption/tests/hooks.php
Normal file
@@ -0,0 +1,462 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Bjoern Schiessle
|
||||
* @copyright 2014 Bjoern Schiessle <schiessle@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../lib/base.php';
|
||||
require_once __DIR__ . '/../lib/crypt.php';
|
||||
require_once __DIR__ . '/../lib/keymanager.php';
|
||||
require_once __DIR__ . '/../lib/stream.php';
|
||||
require_once __DIR__ . '/../lib/util.php';
|
||||
require_once __DIR__ . '/../appinfo/app.php';
|
||||
require_once __DIR__ . '/util.php';
|
||||
|
||||
use OCA\Encryption;
|
||||
|
||||
/**
|
||||
* Class Test_Encryption_Hooks
|
||||
* @brief this class provide basic hook app tests
|
||||
*/
|
||||
class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
const TEST_ENCRYPTION_HOOKS_USER1 = "test-encryption-hooks-user1.dot";
|
||||
const TEST_ENCRYPTION_HOOKS_USER2 = "test-encryption-hooks-user2.dot";
|
||||
|
||||
/**
|
||||
* @var \OC_FilesystemView
|
||||
*/
|
||||
public $user1View; // view on /data/user1/files
|
||||
public $user2View; // view on /data/user2/files
|
||||
public $rootView; // view on /data/user
|
||||
public $data;
|
||||
public $filename;
|
||||
public $folder;
|
||||
|
||||
private static $testFiles;
|
||||
|
||||
public static function setUpBeforeClass() {
|
||||
// note: not using a data provider because these
|
||||
// files all need to coexist to make sure the
|
||||
// share keys are found properly (pattern matching)
|
||||
self::$testFiles = array(
|
||||
't est.txt',
|
||||
't est_.txt',
|
||||
't est.doc.txt',
|
||||
't est(.*).txt', // make sure the regexp is escaped
|
||||
'multiple.dots.can.happen.too.txt',
|
||||
't est.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.txt',
|
||||
't est_.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey.txt',
|
||||
'who would upload their.shareKey',
|
||||
'user ones file.txt',
|
||||
'user ones file.txt.backup',
|
||||
'.t est.txt'
|
||||
);
|
||||
|
||||
// reset backend
|
||||
\OC_User::clearBackends();
|
||||
\OC_User::useBackend('database');
|
||||
|
||||
\OC_Hook::clear('OC_Filesystem');
|
||||
\OC_Hook::clear('OC_User');
|
||||
|
||||
// clear share hooks
|
||||
\OC_Hook::clear('OCP\\Share');
|
||||
\OC::registerShareHooks();
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup');
|
||||
|
||||
// Filesystem related hooks
|
||||
\OCA\Encryption\Helper::registerFilesystemHooks();
|
||||
|
||||
// Sharing related hooks
|
||||
\OCA\Encryption\Helper::registerShareHooks();
|
||||
|
||||
// clear and register proxies
|
||||
\OC_FileProxy::clearProxies();
|
||||
\OC_FileProxy::register(new OCA\Encryption\Proxy());
|
||||
|
||||
// create test user
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1, true);
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2, true);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
// set user id
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1);
|
||||
\OC_User::setUserId(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1);
|
||||
|
||||
// init filesystem view
|
||||
$this->user1View = new \OC_FilesystemView('/'. \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1 . '/files');
|
||||
$this->user2View = new \OC_FilesystemView('/'. \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2 . '/files');
|
||||
$this->rootView = new \OC_FilesystemView('/');
|
||||
|
||||
// init short data
|
||||
$this->data = 'hats';
|
||||
$this->filename = 'enc_hooks_tests-' . uniqid() . '.txt';
|
||||
$this->folder = 'enc_hooks_tests_folder-' . uniqid();
|
||||
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass() {
|
||||
// cleanup test user
|
||||
\OC_User::deleteUser(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1);
|
||||
\OC_User::deleteUser(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2);
|
||||
}
|
||||
|
||||
function testDeleteHooks() {
|
||||
|
||||
// remember files_trashbin state
|
||||
$stateFilesTrashbin = OC_App::isEnabled('files_trashbin');
|
||||
|
||||
// we want to tests with app files_trashbin disabled
|
||||
\OC_App::disable('files_trashbin');
|
||||
|
||||
// make sure that the trash bin is disabled
|
||||
$this->assertFalse(\OC_APP::isEnabled('files_trashbin'));
|
||||
|
||||
$this->user1View->file_put_contents($this->filename, $this->data);
|
||||
|
||||
// check if all keys are generated
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->filename . '.key'));
|
||||
|
||||
|
||||
\Test_Encryption_Util::logoutHelper();
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2);
|
||||
\OC_User::setUserId(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2);
|
||||
|
||||
|
||||
$this->user2View->file_put_contents($this->filename, $this->data);
|
||||
|
||||
// check if all keys are generated
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/keyfiles/' . $this->filename . '.key'));
|
||||
|
||||
|
||||
// create a dummy file that we can delete something outside of data/user/files
|
||||
// in this case no share or file keys should be deleted
|
||||
$this->rootView->file_put_contents(self::TEST_ENCRYPTION_HOOKS_USER2 . "/" . $this->filename, $this->data);
|
||||
|
||||
// delete dummy file outside of data/user/files
|
||||
$this->rootView->unlink(self::TEST_ENCRYPTION_HOOKS_USER2 . "/" . $this->filename);
|
||||
|
||||
// all keys should still exist
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/keyfiles/' . $this->filename . '.key'));
|
||||
|
||||
|
||||
// delete the file in data/user/files
|
||||
// now the correspondig share and file keys from user2 should be deleted
|
||||
$this->user2View->unlink($this->filename);
|
||||
|
||||
// check if keys from user2 are really deleted
|
||||
$this->assertFalse($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2 . '.shareKey'));
|
||||
$this->assertFalse($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/keyfiles/' . $this->filename . '.key'));
|
||||
|
||||
// but user1 keys should still exist
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->filename . '.key'));
|
||||
|
||||
if ($stateFilesTrashbin) {
|
||||
OC_App::enable('files_trashbin');
|
||||
}
|
||||
else {
|
||||
OC_App::disable('files_trashbin');
|
||||
}
|
||||
}
|
||||
|
||||
function testDeleteHooksForSharedFiles() {
|
||||
|
||||
\Test_Encryption_Util::logoutHelper();
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1);
|
||||
\OC_User::setUserId(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1);
|
||||
|
||||
// remember files_trashbin state
|
||||
$stateFilesTrashbin = OC_App::isEnabled('files_trashbin');
|
||||
|
||||
// we want to tests with app files_trashbin disabled
|
||||
\OC_App::disable('files_trashbin');
|
||||
|
||||
// make sure that the trash bin is disabled
|
||||
$this->assertFalse(\OC_APP::isEnabled('files_trashbin'));
|
||||
|
||||
$this->user1View->file_put_contents($this->filename, $this->data);
|
||||
|
||||
// check if all keys are generated
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->filename . '.key'));
|
||||
|
||||
// get the file info from previous created file
|
||||
$fileInfo = $this->user1View->getFileInfo($this->filename);
|
||||
|
||||
// check if we have a valid file info
|
||||
$this->assertTrue(is_array($fileInfo));
|
||||
|
||||
// share the file with user2
|
||||
\OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, self::TEST_ENCRYPTION_HOOKS_USER2, OCP\PERMISSION_ALL);
|
||||
|
||||
// check if new share key exists
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2 . '.shareKey'));
|
||||
|
||||
\Test_Encryption_Util::logoutHelper();
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2);
|
||||
\OC_User::setUserId(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2);
|
||||
|
||||
// user2 has a local file with the same name
|
||||
$this->user2View->file_put_contents($this->filename, $this->data);
|
||||
|
||||
// check if all keys are generated
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/keyfiles/' . $this->filename . '.key'));
|
||||
|
||||
// delete the Shared file from user1 in data/user2/files/Shared
|
||||
$this->user2View->unlink('/Shared/' . $this->filename);
|
||||
|
||||
// now keys from user1s home should be gone
|
||||
$this->assertFalse($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
// FIXME: key is not properly removed
|
||||
/*
|
||||
$this->assertFalse($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2 . '.shareKey'));
|
||||
*/
|
||||
$this->assertFalse($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->filename . '.key'));
|
||||
|
||||
// but user2 keys should still exist
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER2 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
self::TEST_ENCRYPTION_HOOKS_USER2 . '/files_encryption/keyfiles/' . $this->filename . '.key'));
|
||||
|
||||
// cleanup
|
||||
|
||||
$this->user2View->unlink($this->filename);
|
||||
|
||||
\Test_Encryption_Util::logoutHelper();
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1);
|
||||
\OC_User::setUserId(\Test_Encryption_Hooks::TEST_ENCRYPTION_HOOKS_USER1);
|
||||
|
||||
// unshare the file
|
||||
\OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, self::TEST_ENCRYPTION_HOOKS_USER2);
|
||||
|
||||
$this->user1View->unlink($this->filename);
|
||||
|
||||
if ($stateFilesTrashbin) {
|
||||
OC_App::enable('files_trashbin');
|
||||
}
|
||||
else {
|
||||
OC_App::disable('files_trashbin');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test rename operation
|
||||
*/
|
||||
function testRenameHook() {
|
||||
// create all files to make sure all keys can coexist properly
|
||||
foreach (self::$testFiles as $file) {
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $file, $this->data);
|
||||
|
||||
// test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
}
|
||||
|
||||
foreach (self::$testFiles as $file) {
|
||||
$this->doTestRenameHook($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* test rename operation
|
||||
*/
|
||||
function doTestRenameHook($filename) {
|
||||
// check if keys exists
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $filename . '.key'));
|
||||
|
||||
// make subfolder and sub-subfolder
|
||||
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder);
|
||||
|
||||
$this->assertTrue($this->rootView->is_dir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder));
|
||||
|
||||
// move the file to the sub-subfolder
|
||||
$root = $this->rootView->getRoot();
|
||||
$this->rootView->chroot('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/');
|
||||
$this->rootView->rename($filename, '/' . $this->folder . '/' . $this->folder . '/' . $filename);
|
||||
$this->rootView->chroot($root);
|
||||
|
||||
$this->assertFalse($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $filename));
|
||||
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder . '/' . $filename));
|
||||
|
||||
// keys should be renamed too
|
||||
$this->assertFalse($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertFalse($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $filename . '.key'));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' . $this->folder . '/' . $this->folder . '/'
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->folder . '/' . $this->folder . '/'
|
||||
. $filename . '.key'));
|
||||
|
||||
// cleanup
|
||||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
}
|
||||
|
||||
function testCopyHook() {
|
||||
// create all files to make sure all keys can coexist properly
|
||||
foreach (self::$testFiles as $file) {
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $file, $this->data);
|
||||
|
||||
// test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
}
|
||||
|
||||
foreach (self::$testFiles as $file) {
|
||||
$this->doTestCopyHook($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* test rename operation
|
||||
*/
|
||||
function doTestCopyHook($filename) {
|
||||
// check if keys exists
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $filename . '.key'));
|
||||
|
||||
// make subfolder and sub-subfolder
|
||||
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder);
|
||||
|
||||
$this->assertTrue($this->rootView->is_dir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder));
|
||||
|
||||
// copy the file to the sub-subfolder
|
||||
\OC\Files\Filesystem::copy($filename, '/' . $this->folder . '/' . $this->folder . '/' . $filename);
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $filename));
|
||||
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder . '/' . $filename));
|
||||
|
||||
// keys should be copied too
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $filename . '.key'));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' . $this->folder . '/' . $this->folder . '/'
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->folder . '/' . $this->folder . '/'
|
||||
. $filename . '.key'));
|
||||
|
||||
// cleanup
|
||||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief replacing encryption keys during password change should be allowed
|
||||
* until the user logged in for the first time
|
||||
*/
|
||||
public function testSetPassphrase() {
|
||||
|
||||
$view = new \OC\Files\View();
|
||||
|
||||
// set user password for the first time
|
||||
\OCA\Encryption\Hooks::postCreateUser(array('uid' => 'newUser', 'password' => 'newUserPassword'));
|
||||
|
||||
$this->assertTrue($view->file_exists('public-keys/newUser.public.key'));
|
||||
$this->assertTrue($view->file_exists('newUser/files_encryption/newUser.private.key'));
|
||||
|
||||
// check if we are able to decrypt the private key
|
||||
$encryptedKey = \OCA\Encryption\Keymanager::getPrivateKey($view, 'newUser');
|
||||
$privateKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, 'newUserPassword');
|
||||
$this->assertTrue(is_string($privateKey));
|
||||
|
||||
// change the password before the user logged-in for the first time,
|
||||
// we can replace the encryption keys
|
||||
\OCA\Encryption\Hooks::setPassphrase(array('uid' => 'newUser', 'password' => 'passwordChanged'));
|
||||
|
||||
$encryptedKey = \OCA\Encryption\Keymanager::getPrivateKey($view, 'newUser');
|
||||
$privateKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, 'passwordChanged');
|
||||
$this->assertTrue(is_string($privateKey));
|
||||
|
||||
// now create a files folder to simulate a already used account
|
||||
$view->mkdir('/newUser/files');
|
||||
|
||||
// change the password after the user logged in, now the password should not change
|
||||
\OCA\Encryption\Hooks::setPassphrase(array('uid' => 'newUser', 'password' => 'passwordChanged2'));
|
||||
|
||||
$encryptedKey = \OCA\Encryption\Keymanager::getPrivateKey($view, 'newUser');
|
||||
$privateKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, 'passwordChanged2');
|
||||
$this->assertFalse($privateKey);
|
||||
|
||||
$privateKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, 'passwordChanged');
|
||||
$this->assertTrue(is_string($privateKey));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,11 +23,11 @@ use OCA\Encryption;
|
||||
*/
|
||||
class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
const TEST_USER = "test-keymanager-user";
|
||||
const TEST_USER = "test-keymanager-user.dot";
|
||||
|
||||
public $userId;
|
||||
public $pass;
|
||||
public $stateFilesTrashbin;
|
||||
public static $stateFilesTrashbin;
|
||||
/**
|
||||
* @var OC_FilesystemView
|
||||
*/
|
||||
@@ -50,6 +50,12 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
// disable file proxy by default
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
// remember files_trashbin state
|
||||
self::$stateFilesTrashbin = OC_App::isEnabled('files_trashbin');
|
||||
|
||||
// we don't want to tests with app files_trashbin enabled
|
||||
\OC_App::disable('files_trashbin');
|
||||
|
||||
// create test user
|
||||
\OC_User::deleteUser(\Test_Encryption_Keymanager::TEST_USER);
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Keymanager::TEST_USER, true);
|
||||
@@ -70,28 +76,17 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$this->view = new \OC_FilesystemView('/');
|
||||
|
||||
\OC_User::setUserId(\Test_Encryption_Keymanager::TEST_USER);
|
||||
\Test_Encryption_Util::loginHelper(Test_Encryption_Keymanager::TEST_USER);
|
||||
$this->userId = \Test_Encryption_Keymanager::TEST_USER;
|
||||
$this->pass = \Test_Encryption_Keymanager::TEST_USER;
|
||||
|
||||
$userHome = \OC_User::getHome($this->userId);
|
||||
$this->dataDir = str_replace('/' . $this->userId, '', $userHome);
|
||||
|
||||
// remember files_trashbin state
|
||||
$this->stateFilesTrashbin = OC_App::isEnabled('files_trashbin');
|
||||
|
||||
// we don't want to tests with app files_trashbin enabled
|
||||
\OC_App::disable('files_trashbin');
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
// reset app files_trashbin
|
||||
if ($this->stateFilesTrashbin) {
|
||||
OC_App::enable('files_trashbin');
|
||||
}
|
||||
else {
|
||||
OC_App::disable('files_trashbin');
|
||||
}
|
||||
$this->view->deleteAll('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys');
|
||||
$this->view->deleteAll('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/keyfiles');
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass() {
|
||||
@@ -99,6 +94,10 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
// cleanup test user
|
||||
\OC_User::deleteUser(\Test_Encryption_Keymanager::TEST_USER);
|
||||
// reset app files_trashbin
|
||||
if (self::$stateFilesTrashbin) {
|
||||
OC_App::enable('files_trashbin');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,6 +135,27 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertArrayHasKey('key', $sslInfo);
|
||||
}
|
||||
|
||||
function fileNameFromShareKeyProvider() {
|
||||
return array(
|
||||
array('file.user.shareKey', 'user', 'file'),
|
||||
array('file.name.with.dots.user.shareKey', 'user', 'file.name.with.dots'),
|
||||
array('file.name.user.with.dots.shareKey', 'user.with.dots', 'file.name'),
|
||||
array('file.txt', 'user', false),
|
||||
array('user.shareKey', 'user', false),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @small
|
||||
*
|
||||
* @dataProvider fileNameFromShareKeyProvider
|
||||
*/
|
||||
function testGetFilenameFromShareKey($fileName, $user, $expectedFileName) {
|
||||
$this->assertEquals($expectedFileName,
|
||||
\TestProtectedKeymanagerMethods::testGetFilenameFromShareKey($fileName, $user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
@@ -143,7 +163,7 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$key = $this->randomKey;
|
||||
|
||||
$file = 'unittest-' . time() . '.txt';
|
||||
$file = 'unittest-' . uniqid() . '.txt';
|
||||
|
||||
$util = new Encryption\Util($this->view, $this->userId);
|
||||
|
||||
@@ -193,44 +213,256 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
function testRecursiveDelShareKeys() {
|
||||
function testRecursiveDelShareKeysFolder() {
|
||||
|
||||
// generate filename
|
||||
$filename = '/tmp-' . time() . '.txt';
|
||||
|
||||
// create folder structure
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1');
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1/subfolder');
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1/subfolder/subsubfolder');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1/existingFile.txt', 'data');
|
||||
|
||||
// enable encryption proxy
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = true;
|
||||
// create folder structure for some dummy share key files
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1');
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder');
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/subsubfolder');
|
||||
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1/subfolder/subsubfolder' . $filename, $this->dataShort);
|
||||
// create some dummy share keys
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user1.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.test.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.test-keymanager-userxdot.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.userx.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.userx.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.user1.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file2.user2.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file2.user3.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/file2.user3.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/subsubfolder/file1.user1.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/subsubfolder/file2.user2.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/subsubfolder/file2.user3.shareKey', 'data');
|
||||
|
||||
// test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
// recursive delete share keys from user1 and user2
|
||||
Encryption\Keymanager::delShareKey($this->view, array('user1', 'user2', Test_Encryption_Keymanager::TEST_USER), '/folder1/');
|
||||
|
||||
// change encryption proxy to previous state
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
// recursive delete keys
|
||||
Encryption\Keymanager::delShareKey($this->view, array('admin'), '/folder1/');
|
||||
|
||||
// check if share key not exists
|
||||
// check if share keys from user1 and user2 are deleted
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/admin/files_encryption/share-keys/folder1/subfolder/subsubfolder/' . $filename . '.admin.shareKey'));
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.user1.shareKey'));
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.shareKey'));
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file2.user2.shareKey'));
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/subsubfolder/file1.user1.shareKey'));
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/subsubfolder/file2.user2.shareKey'));
|
||||
|
||||
// enable encryption proxy
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = true;
|
||||
// check if share keys from user3 still exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file2.user3.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/subsubfolder/file2.user3.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/file2.user3.shareKey'));
|
||||
|
||||
// check if share keys for user or file with similar name
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.test.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.test-keymanager-userxdot.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.userx.shareKey'));
|
||||
// FIXME: this case currently cannot be distinguished, needs further fixing
|
||||
/*
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.userx.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.user1.shareKey'));
|
||||
*/
|
||||
|
||||
// owner key from existing file should still exists because the file is still there
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink('/admin/files/folder1');
|
||||
$this->view->deleteAll('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
function testRecursiveDelShareKeysFile() {
|
||||
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1/existingFile.txt', 'data');
|
||||
|
||||
// create folder structure for some dummy share key files
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1');
|
||||
|
||||
// create some dummy share keys
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user1.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user2.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user3.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
|
||||
|
||||
// recursive delete share keys from user1 and user2
|
||||
Encryption\Keymanager::delShareKey($this->view, array('user1', 'user2', Test_Encryption_Keymanager::TEST_USER), '/folder1/existingFile.txt');
|
||||
|
||||
// check if share keys from user1 and user2 are deleted
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.user1.shareKey'));
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.user2.shareKey'));
|
||||
|
||||
// check if share keys for user3 and owner
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user3.shareKey'));
|
||||
// cleanup
|
||||
$this->view->deleteAll('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
function testDeleteFileKey() {
|
||||
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1/existingFile.txt', 'data');
|
||||
|
||||
// create folder structure for some dummy file key files
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/keyfiles/folder1');
|
||||
|
||||
// create dummy keyfile
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/keyfiles/folder1/dummyFile.txt.key', 'data');
|
||||
|
||||
// recursive delete share keys from user1 and user2
|
||||
$result = Encryption\Keymanager::deleteFileKey($this->view, '/folder1/existingFile.txt');
|
||||
$this->assertFalse($result);
|
||||
|
||||
$result2 = Encryption\Keymanager::deleteFileKey($this->view, '/folder1/dummyFile.txt');
|
||||
$this->assertTrue($result2);
|
||||
|
||||
// check if file key from dummyFile was deleted
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/keyfiles/folder1/dummyFile.txt.key'));
|
||||
|
||||
// check if file key from existing file still exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/keyfiles/folder1/existingFile.txt.key'));
|
||||
|
||||
// cleanup
|
||||
$this->view->deleteAll('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
function testDeleteFileKeyFolder() {
|
||||
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1/existingFile.txt', 'data');
|
||||
|
||||
// create folder structure for some dummy file key files
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/keyfiles/folder1');
|
||||
|
||||
// create dummy keyfile
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/keyfiles/folder1/dummyFile.txt.key', 'data');
|
||||
|
||||
// recursive delete share keys from user1 and user2
|
||||
$result = Encryption\Keymanager::deleteFileKey($this->view, '/folder1');
|
||||
$this->assertFalse($result);
|
||||
|
||||
// all file keys should still exists if we try to delete a folder with keys for which some files still exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/keyfiles/folder1/dummyFile.txt.key'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/keyfiles/folder1/existingFile.txt.key'));
|
||||
|
||||
// delete folder
|
||||
$this->view->unlink('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1');
|
||||
// create dummy keyfile
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/keyfiles/folder1/dummyFile.txt.key', 'data');
|
||||
|
||||
// now file keys should be deleted since the folder no longer exists
|
||||
$result = Encryption\Keymanager::deleteFileKey($this->view, '/folder1');
|
||||
$this->assertTrue($result);
|
||||
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/keyfiles/folder1'));
|
||||
|
||||
// cleanup
|
||||
$this->view->deleteAll('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1');
|
||||
|
||||
}
|
||||
|
||||
function testDelAllShareKeysFile() {
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1/existingFile.txt', 'data');
|
||||
|
||||
// create folder structure for some dummy share key files
|
||||
$this->view->mkdir('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1');
|
||||
|
||||
// create some dummy share keys for the existing file
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user1.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user2.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user3.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
|
||||
|
||||
// create some dummy share keys for a non-existing file
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user1.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user2.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user3.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
|
||||
|
||||
// try to del all share keys from a existing file, should fail because the file still exists
|
||||
$result = Encryption\Keymanager::delAllShareKeys($this->view, Test_Encryption_Keymanager::TEST_USER, 'folder1/existingFile.txt');
|
||||
$this->assertFalse($result);
|
||||
|
||||
// check if share keys still exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user1.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user2.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user3.shareKey'));
|
||||
|
||||
// try to del all share keys from file, should succeed because the does not exist any more
|
||||
$result2 = Encryption\Keymanager::delAllShareKeys($this->view, Test_Encryption_Keymanager::TEST_USER, 'folder1/nonexistingFile.txt');
|
||||
$this->assertTrue($result2);
|
||||
|
||||
// check if share keys are really gone
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
|
||||
// check that it only deleted keys or users who had access, others remain
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user1.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user2.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user3.shareKey'));
|
||||
|
||||
// cleanup
|
||||
$this->view->deleteAll('/'.Test_Encryption_Keymanager::TEST_USER.'/files/folder1');
|
||||
|
||||
// change encryption proxy to previous state
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* dummy class to access protected methods of \OCA\Encryption\Keymanager for testing
|
||||
*/
|
||||
class TestProtectedKeymanagerMethods extends \OCA\Encryption\Keymanager {
|
||||
|
||||
/**
|
||||
* @param string $sharekey
|
||||
*/
|
||||
public static function testGetFilenameFromShareKey($sharekey, $user) {
|
||||
return self::getFilenameFromShareKey($sharekey, $user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,10 @@ class Test_Encryption_Proxy extends \PHPUnit_Framework_TestCase {
|
||||
/**
|
||||
* @var \OC_FilesystemView
|
||||
*/
|
||||
public $view;
|
||||
public $view; // view in /data/user/files
|
||||
public $rootView; // view on /data/user
|
||||
public $data;
|
||||
public $filename;
|
||||
|
||||
public static function setUpBeforeClass() {
|
||||
// reset backend
|
||||
@@ -74,9 +76,12 @@ class Test_Encryption_Proxy extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
// init filesystem view
|
||||
$this->view = new \OC_FilesystemView('/'. \Test_Encryption_Proxy::TEST_ENCRYPTION_PROXY_USER1 . '/files');
|
||||
$this->rootView = new \OC_FilesystemView('/'. \Test_Encryption_Proxy::TEST_ENCRYPTION_PROXY_USER1 );
|
||||
|
||||
// init short data
|
||||
$this->data = 'hats';
|
||||
$this->filename = 'enc_proxy_tests-' . uniqid() . '.txt';
|
||||
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass() {
|
||||
@@ -90,21 +95,41 @@ class Test_Encryption_Proxy extends \PHPUnit_Framework_TestCase {
|
||||
*/
|
||||
function testPostFileSize() {
|
||||
|
||||
// generate filename
|
||||
$filename = 'tmp-' . time() . '.txt';
|
||||
|
||||
$this->view->file_put_contents($filename, $this->data);
|
||||
$this->view->file_put_contents($this->filename, $this->data);
|
||||
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$unencryptedSize = $this->view->filesize($filename);
|
||||
$unencryptedSize = $this->view->filesize($this->filename);
|
||||
|
||||
\OC_FileProxy::$enabled = true;
|
||||
|
||||
$encryptedSize = $this->view->filesize($filename);
|
||||
$encryptedSize = $this->view->filesize($this->filename);
|
||||
|
||||
$this->assertTrue($encryptedSize !== $unencryptedSize);
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink($this->filename);
|
||||
|
||||
}
|
||||
|
||||
function testPostFileSizeWithDirectory() {
|
||||
|
||||
$this->view->file_put_contents($this->filename, $this->data);
|
||||
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
// get root size, must match the file's unencrypted size
|
||||
$unencryptedSize = $this->view->filesize('');
|
||||
|
||||
\OC_FileProxy::$enabled = true;
|
||||
|
||||
$encryptedSize = $this->view->filesize('');
|
||||
|
||||
$this->assertTrue($encryptedSize !== $unencryptedSize);
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink($this->filename);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
\OC_Appconfig::setValue('core', 'shareapi_allow_resharing', 'yes');
|
||||
|
||||
// clear share hooks
|
||||
\OC_Hook::clear('OCP\\Share');
|
||||
\OC_Hook::clear();
|
||||
\OC::registerShareHooks();
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup');
|
||||
|
||||
@@ -127,6 +127,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
\OC_User::deleteUser(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @medium
|
||||
* @param bool $withTeardown
|
||||
@@ -194,8 +195,9 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
. $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink(
|
||||
'/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
|
||||
$this->view->chroot('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/');
|
||||
$this->view->unlink($this->filename);
|
||||
$this->view->chroot('/');
|
||||
|
||||
// check if share key not exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
@@ -265,8 +267,9 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
. $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink(
|
||||
'/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
|
||||
$this->view->chroot('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/');
|
||||
$this->view->unlink($this->filename);
|
||||
$this->view->chroot('/');
|
||||
|
||||
// check if share key not exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
@@ -352,7 +355,9 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
. $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1);
|
||||
$this->view->chroot('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files');
|
||||
$this->view->unlink($this->folder1);
|
||||
$this->view->chroot('/');
|
||||
|
||||
// check if share key not exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
@@ -482,9 +487,9 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
. $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink(
|
||||
'/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files' . $this->folder1 . $this->subfolder
|
||||
. $this->subsubfolder . '/' . $this->filename);
|
||||
$this->view->chroot('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files');
|
||||
$this->view->unlink($this->folder1 . $this->subfolder . $this->subsubfolder . '/' . $this->filename);
|
||||
$this->view->chroot('/');
|
||||
|
||||
// check if share key not exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
@@ -494,6 +499,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function testPublicShareFile() {
|
||||
// login as admin
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
|
||||
@@ -559,7 +565,9 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
. $this->filename . '.' . $publicShareKeyId . '.shareKey'));
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
|
||||
$this->view->chroot('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/');
|
||||
$this->view->unlink($this->filename);
|
||||
$this->view->chroot('/');
|
||||
|
||||
// check if share key not exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
@@ -636,7 +644,9 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
. $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER4 . '.shareKey'));
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
|
||||
$this->view->chroot('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/');
|
||||
$this->view->unlink($this->filename);
|
||||
$this->view->chroot('/');
|
||||
|
||||
// check if share key not exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
@@ -649,9 +659,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
* @large
|
||||
*/
|
||||
function testRecoveryFile() {
|
||||
$this->markTestIncomplete(
|
||||
'No idea what\'s wrong here, this works perfectly in real-world. removeRecoveryKeys(\'/\') L709 removes correctly the keys, but for some reasons afterwards also the top-level folder "share-keys" is gone...'
|
||||
);
|
||||
|
||||
// login as admin
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
|
||||
|
||||
@@ -733,8 +741,10 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
. $this->filename . '.' . $recoveryKeyId . '.shareKey'));
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
|
||||
$this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->folder1);
|
||||
$this->view->chroot('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/');
|
||||
$this->view->unlink($this->filename);
|
||||
$this->view->unlink($this->folder1);
|
||||
$this->view->chroot('/');
|
||||
|
||||
// check if share key for recovery not exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
@@ -754,13 +764,13 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
* @large
|
||||
*/
|
||||
function testRecoveryForUser() {
|
||||
$this->markTestIncomplete(
|
||||
'This test drives Jenkins crazy - "Cannot modify header information - headers already sent" - line 811'
|
||||
);
|
||||
|
||||
// login as admin
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
|
||||
|
||||
\OCA\Encryption\Helper::adminEnableRecovery(null, 'test123');
|
||||
$result = \OCA\Encryption\Helper::adminEnableRecovery(null, 'test123');
|
||||
$this->assertTrue($result);
|
||||
|
||||
$recoveryKeyId = OC_Appconfig::getValue('files_encryption', 'recoveryKeyId');
|
||||
|
||||
// login as user2
|
||||
@@ -771,6 +781,9 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
// enable recovery for admin
|
||||
$this->assertTrue($util->setRecoveryForUser(1));
|
||||
|
||||
// add recovery keys for existing files (e.g. the auto-generated welcome.txt)
|
||||
$util->addRecoveryKeys();
|
||||
|
||||
// create folder structure
|
||||
$this->view->mkdir('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files' . $this->folder1);
|
||||
$this->view->mkdir(
|
||||
@@ -809,6 +822,10 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
// change password
|
||||
\OC_User::setPassword(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, 'test', 'test123');
|
||||
$params = array('uid' => \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2,
|
||||
'password' => 'test',
|
||||
'recoveryPassword' => 'test123');
|
||||
\OCA\Encryption\Hooks::setPassphrase($params);
|
||||
|
||||
// login as user2
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, false, 'test');
|
||||
@@ -823,8 +840,10 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertEquals($this->dataShort, $retrievedCryptedFile2);
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files' . $this->folder1);
|
||||
$this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files' . $this->filename);
|
||||
$this->view->chroot('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/');
|
||||
$this->view->unlink($this->folder1);
|
||||
$this->view->unlink($this->filename);
|
||||
$this->view->chroot('/');
|
||||
|
||||
// check if share key for user and recovery exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
@@ -842,11 +861,21 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
. $this->subfolder . $this->subsubfolder . '/'
|
||||
. $this->filename . '.' . $recoveryKeyId . '.shareKey'));
|
||||
|
||||
// enable recovery for admin
|
||||
|
||||
// disable recovery for admin
|
||||
$this->assertTrue($util->setRecoveryForUser(0));
|
||||
|
||||
\OCA\Encryption\Helper::adminDisableRecovery('test123');
|
||||
|
||||
$this->assertEquals(0, \OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled'));
|
||||
|
||||
//clean up, reset passwords
|
||||
\OC_User::setPassword(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, 'test123');
|
||||
$params = array('uid' => \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2,
|
||||
'password' => \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2,
|
||||
'recoveryPassword' => 'test123');
|
||||
\OCA\Encryption\Hooks::setPassphrase($params);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -889,8 +918,8 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
} catch (Exception $e) {
|
||||
$this->assertEquals(0, strpos($e->getMessage(), "Following users are not set up for encryption"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// login as admin
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
|
||||
|
||||
@@ -925,7 +954,177 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
. $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey'));
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
|
||||
$this->view->chroot('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/');
|
||||
$this->view->unlink($this->filename);
|
||||
$this->view->chroot('/');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief test moving a shared file out of the Shared folder
|
||||
*/
|
||||
function testRename() {
|
||||
|
||||
// login as admin
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
|
||||
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename, $this->dataShort);
|
||||
|
||||
// test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
|
||||
// get the file info from previous created file
|
||||
$fileInfo = $this->view->getFileInfo(
|
||||
'/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
|
||||
|
||||
// check if we have a valid file info
|
||||
$this->assertTrue(is_array($fileInfo));
|
||||
|
||||
// share the file
|
||||
\OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, OCP\PERMISSION_ALL);
|
||||
|
||||
// check if share key for user2exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
|
||||
// login as user2
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2);
|
||||
|
||||
$this->assertTrue($this->view->file_exists('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename));
|
||||
|
||||
// get file contents
|
||||
$retrievedCryptedFile = $this->view->file_get_contents(
|
||||
'/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename);
|
||||
|
||||
// check if data is the same as we previously written
|
||||
$this->assertEquals($this->dataShort, $retrievedCryptedFile);
|
||||
|
||||
// move the file out of the shared folder
|
||||
$this->view->rename('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/Shared/' . $this->filename,
|
||||
'/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename);
|
||||
|
||||
// check if we can read the moved file
|
||||
$retrievedRenamedFile = $this->view->file_get_contents(
|
||||
'/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename);
|
||||
|
||||
// check if data is the same as we previously written
|
||||
$this->assertEquals($this->dataShort, $retrievedRenamedFile);
|
||||
|
||||
// the owners file should be deleted
|
||||
$this->assertFalse($this->view->file_exists('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename));
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* test if additional share keys are added if we move a folder to a shared parent
|
||||
* @medium
|
||||
*/
|
||||
function testMoveFolder() {
|
||||
|
||||
// login as admin
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
|
||||
|
||||
$view = new \OC\Files\View('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
|
||||
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$folder = '/folder' . uniqid();
|
||||
|
||||
\OC\Files\Filesystem::mkdir($folder);
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = \OC\Files\Filesystem::file_put_contents($folder . $filename, $this->dataShort);
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
|
||||
// Get file decrypted contents
|
||||
$decrypt = \OC\Files\Filesystem::file_get_contents($folder . $filename);
|
||||
|
||||
$this->assertEquals($this->dataShort, $decrypt);
|
||||
|
||||
$newFolder = '/newfolder/subfolder' . uniqid();
|
||||
\OC\Files\Filesystem::mkdir('/newfolder');
|
||||
|
||||
// get the file info from previous created file
|
||||
$fileInfo = \OC\Files\Filesystem::getFileInfo('/newfolder');
|
||||
$this->assertTrue(is_array($fileInfo));
|
||||
|
||||
// share the folder
|
||||
\OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, OCP\PERMISSION_ALL);
|
||||
|
||||
\OC\Files\Filesystem::rename($folder, $newFolder);
|
||||
|
||||
// Get file decrypted contents
|
||||
$newDecrypt = \OC\Files\Filesystem::file_get_contents($newFolder . $filename);
|
||||
$this->assertEquals($this->dataShort, $newDecrypt);
|
||||
|
||||
// check if additional share key for user2 exists
|
||||
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $newFolder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// check that old keys were removed/moved properly
|
||||
$this->assertFalse($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// tear down
|
||||
\OC\Files\Filesystem::unlink($newFolder);
|
||||
\OC\Files\Filesystem::unlink('/newfolder');
|
||||
}
|
||||
|
||||
function testMoveFileToFolder() {
|
||||
|
||||
$view = new \OC\Files\View('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
|
||||
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$folder = '/folder' . uniqid();
|
||||
|
||||
\OC\Files\Filesystem::mkdir($folder);
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = \OC\Files\Filesystem::file_put_contents($folder . $filename, $this->dataShort);
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
|
||||
// Get file decrypted contents
|
||||
$decrypt = \OC\Files\Filesystem::file_get_contents($folder . $filename);
|
||||
|
||||
$this->assertEquals($this->dataShort, $decrypt);
|
||||
|
||||
$subFolder = $folder . '/subfolder' . uniqid();
|
||||
\OC\Files\Filesystem::mkdir($subFolder);
|
||||
|
||||
// get the file info from previous created file
|
||||
$fileInfo = \OC\Files\Filesystem::getFileInfo($folder);
|
||||
|
||||
// share the folder
|
||||
\OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, OCP\PERMISSION_ALL);
|
||||
|
||||
// check that the share keys exist
|
||||
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
|
||||
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// move the file into the subfolder
|
||||
\OC\Files\Filesystem::rename($folder . $filename, $subFolder . $filename);
|
||||
|
||||
// Get file decrypted contents
|
||||
$newDecrypt = \OC\Files\Filesystem::file_get_contents($subFolder . $filename);
|
||||
$this->assertEquals($this->dataShort, $newDecrypt);
|
||||
|
||||
// check if additional share key for user2 exists
|
||||
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $subFolder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
|
||||
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $subFolder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// check that old keys were removed/moved properly
|
||||
$this->assertFalse($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
|
||||
$this->assertFalse($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// tear down
|
||||
\OC\Files\Filesystem::unlink($subFolder);
|
||||
\OC\Files\Filesystem::unlink($folder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ class Test_Encryption_Stream extends \PHPUnit_Framework_TestCase {
|
||||
}
|
||||
|
||||
function testStreamOptions() {
|
||||
$filename = '/tmp-' . time();
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$view = new \OC\Files\View('/' . $this->userId . '/files');
|
||||
|
||||
// Save short data as encrypted file using stream wrapper
|
||||
@@ -122,7 +122,7 @@ class Test_Encryption_Stream extends \PHPUnit_Framework_TestCase {
|
||||
}
|
||||
|
||||
function testStreamSetBlocking() {
|
||||
$filename = '/tmp-' . time();
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$view = new \OC\Files\View('/' . $this->userId . '/files');
|
||||
|
||||
// Save short data as encrypted file using stream wrapper
|
||||
@@ -136,6 +136,8 @@ class Test_Encryption_Stream extends \PHPUnit_Framework_TestCase {
|
||||
// set stream options
|
||||
$this->assertTrue(stream_set_blocking($handle, 1));
|
||||
|
||||
fclose($handle);
|
||||
|
||||
// tear down
|
||||
$view->unlink($filename);
|
||||
}
|
||||
@@ -144,7 +146,7 @@ class Test_Encryption_Stream extends \PHPUnit_Framework_TestCase {
|
||||
* @medium
|
||||
*/
|
||||
function testStreamSetTimeout() {
|
||||
$filename = '/tmp-' . time();
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$view = new \OC\Files\View('/' . $this->userId . '/files');
|
||||
|
||||
// Save short data as encrypted file using stream wrapper
|
||||
@@ -158,12 +160,14 @@ class Test_Encryption_Stream extends \PHPUnit_Framework_TestCase {
|
||||
// set stream options
|
||||
$this->assertFalse(stream_set_timeout($handle, 1));
|
||||
|
||||
fclose($handle);
|
||||
|
||||
// tear down
|
||||
$view->unlink($filename);
|
||||
}
|
||||
|
||||
function testStreamSetWriteBuffer() {
|
||||
$filename = '/tmp-' . time();
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$view = new \OC\Files\View('/' . $this->userId . '/files');
|
||||
|
||||
// Save short data as encrypted file using stream wrapper
|
||||
@@ -177,7 +181,48 @@ class Test_Encryption_Stream extends \PHPUnit_Framework_TestCase {
|
||||
// set stream options
|
||||
$this->assertEquals(0, stream_set_write_buffer($handle, 1024));
|
||||
|
||||
fclose($handle);
|
||||
|
||||
// tear down
|
||||
$view->unlink($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
* @brief test if stream wrapper can read files outside from the data folder
|
||||
*/
|
||||
function testStreamFromLocalFile() {
|
||||
|
||||
$filename = '/' . $this->userId . '/files/' . 'tmp-' . time().'.txt';
|
||||
|
||||
$tmpFilename = "/tmp/" . time() . ".txt";
|
||||
|
||||
// write an encrypted file
|
||||
$cryptedFile = $this->view->file_put_contents($filename, $this->dataShort);
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
|
||||
// create a copy outside of the data folder in /tmp
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
$encryptedContent = $this->view->file_get_contents($filename);
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
file_put_contents($tmpFilename, $encryptedContent);
|
||||
|
||||
\OCA\Encryption\Helper::addTmpFileToMapper($tmpFilename, $filename);
|
||||
|
||||
// try to read the file from /tmp
|
||||
$handle = fopen("crypt://".$tmpFilename, "r");
|
||||
$contentFromTmpFile = stream_get_contents($handle);
|
||||
|
||||
// check if it was successful
|
||||
$this->assertEquals($this->dataShort, $contentFromTmpFile);
|
||||
|
||||
// clean up
|
||||
unlink($tmpFilename);
|
||||
$this->view->unlink($filename);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,25 +119,34 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
|
||||
function testDeleteFile() {
|
||||
|
||||
// generate filename
|
||||
$filename = 'tmp-' . time() . '.txt';
|
||||
$filename = 'tmp-' . uniqid() . '.txt';
|
||||
$filename2 = $filename . '.backup'; // a second file with similar name
|
||||
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename, $this->dataShort);
|
||||
$cryptedFile2 = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename2, $this->dataShort);
|
||||
|
||||
// test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
$this->assertTrue(is_int($cryptedFile2));
|
||||
|
||||
// check if key for admin exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename
|
||||
. '.key'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename2
|
||||
. '.key'));
|
||||
|
||||
// check if share key for admin exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename2 . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
|
||||
// delete file
|
||||
// delete first file
|
||||
\OC\FIles\Filesystem::unlink($filename);
|
||||
|
||||
// check if file not exists
|
||||
@@ -154,6 +163,20 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
|
||||
// check that second file still exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $filename2));
|
||||
|
||||
// check that key for second file still exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename2
|
||||
. '.key'));
|
||||
|
||||
// check that share key for second file still exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename2 . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
|
||||
// get files
|
||||
$trashFiles = $this->view->getDirectoryContent(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/files/');
|
||||
@@ -179,41 +202,75 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/share-keys/' . $filename
|
||||
. '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey.' . $trashFileSuffix));
|
||||
|
||||
// return filename for next test
|
||||
return $filename . '.' . $trashFileSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
* @brief test restore file
|
||||
*
|
||||
* @depends testDeleteFile
|
||||
* test restore file
|
||||
*/
|
||||
function testRestoreFile($filename) {
|
||||
function testRestoreFile() {
|
||||
// generate filename
|
||||
$filename = 'tmp-' . uniqid() . '.txt';
|
||||
$filename2 = $filename . '.backup'; // a second file with similar name
|
||||
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename, $this->dataShort);
|
||||
$cryptedFile2 = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename2, $this->dataShort);
|
||||
|
||||
// delete both files
|
||||
\OC\Files\Filesystem::unlink($filename);
|
||||
\OC\Files\Filesystem::unlink($filename2);
|
||||
|
||||
$trashFiles = $this->view->getDirectoryContent(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/files/');
|
||||
|
||||
$trashFileSuffix = null;
|
||||
$trashFileSuffix2 = null;
|
||||
// find created file with timestamp
|
||||
foreach ($trashFiles as $file) {
|
||||
if (strncmp($file['path'], $filename, strlen($filename))) {
|
||||
$path_parts = pathinfo($file['name']);
|
||||
$trashFileSuffix = $path_parts['extension'];
|
||||
}
|
||||
if (strncmp($file['path'], $filename2, strlen($filename2))) {
|
||||
$path_parts = pathinfo($file['name']);
|
||||
$trashFileSuffix2 = $path_parts['extension'];
|
||||
}
|
||||
}
|
||||
|
||||
// prepare file information
|
||||
$path_parts = pathinfo($filename);
|
||||
$trashFileSuffix = $path_parts['extension'];
|
||||
$timestamp = str_replace('d', '', $trashFileSuffix);
|
||||
$fileNameWithoutSuffix = str_replace('.' . $trashFileSuffix, '', $filename);
|
||||
|
||||
// restore file
|
||||
$this->assertTrue(\OCA\Files_Trashbin\Trashbin::restore($filename, $fileNameWithoutSuffix, $timestamp));
|
||||
// restore first file
|
||||
$this->assertTrue(\OCA\Files_Trashbin\Trashbin::restore($filename . '.' . $trashFileSuffix, $filename, $timestamp));
|
||||
|
||||
// check if file exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $fileNameWithoutSuffix));
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $filename));
|
||||
|
||||
// check if key for admin exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/'
|
||||
. $fileNameWithoutSuffix . '.key'));
|
||||
. $filename . '.key'));
|
||||
|
||||
// check if share key for admin exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $fileNameWithoutSuffix . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
|
||||
// check that second file was NOT restored
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $filename2));
|
||||
|
||||
// check if key for admin exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/'
|
||||
. $filename2 . '.key'));
|
||||
|
||||
// check if share key for admin exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename2 . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,7 +280,7 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
|
||||
function testPermanentDeleteFile() {
|
||||
|
||||
// generate filename
|
||||
$filename = 'tmp-' . time() . '.txt';
|
||||
$filename = 'tmp-' . uniqid() . '.txt';
|
||||
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' .$this->userId. '/files/' . $filename, $this->dataShort);
|
||||
@@ -242,7 +299,7 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
|
||||
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
|
||||
// delete file
|
||||
\OC\FIles\Filesystem::unlink($filename);
|
||||
\OC\Files\Filesystem::unlink($filename);
|
||||
|
||||
// check if file not exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
|
||||
@@ -22,6 +22,9 @@ use OCA\Encryption;
|
||||
class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
const TEST_ENCRYPTION_UTIL_USER1 = "test-util-user1";
|
||||
const TEST_ENCRYPTION_UTIL_USER2 = "test-util-user2";
|
||||
const TEST_ENCRYPTION_UTIL_GROUP1 = "test-util-group1";
|
||||
const TEST_ENCRYPTION_UTIL_GROUP2 = "test-util-group2";
|
||||
const TEST_ENCRYPTION_UTIL_LEGACY_USER = "test-legacy-user";
|
||||
|
||||
public $userId;
|
||||
@@ -50,20 +53,25 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
\OC_User::clearBackends();
|
||||
\OC_User::useBackend('database');
|
||||
|
||||
// Filesystem related hooks
|
||||
\OCA\Encryption\Helper::registerFilesystemHooks();
|
||||
|
||||
// clear and register hooks
|
||||
\OC_FileProxy::clearProxies();
|
||||
\OC_FileProxy::register(new OCA\Encryption\Proxy());
|
||||
self::setupHooks();
|
||||
|
||||
// create test user
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1, true);
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER2, true);
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER, true);
|
||||
|
||||
// create groups
|
||||
\OC_Group::createGroup(self::TEST_ENCRYPTION_UTIL_GROUP1);
|
||||
\OC_Group::createGroup(self::TEST_ENCRYPTION_UTIL_GROUP2);
|
||||
|
||||
// add user 1 to group1
|
||||
\OC_Group::addToGroup(self::TEST_ENCRYPTION_UTIL_USER1, self::TEST_ENCRYPTION_UTIL_GROUP1);
|
||||
}
|
||||
|
||||
|
||||
function setUp() {
|
||||
// login user
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1);
|
||||
\OC_User::setUserId(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1);
|
||||
$this->userId = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1;
|
||||
$this->pass = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1;
|
||||
@@ -114,7 +122,20 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
public static function tearDownAfterClass() {
|
||||
// cleanup test user
|
||||
\OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1);
|
||||
\OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER2);
|
||||
\OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER);
|
||||
//cleanup groups
|
||||
\OC_Group::deleteGroup(self::TEST_ENCRYPTION_UTIL_GROUP1);
|
||||
\OC_Group::deleteGroup(self::TEST_ENCRYPTION_UTIL_GROUP2);
|
||||
}
|
||||
|
||||
public static function setupHooks() {
|
||||
// Filesystem related hooks
|
||||
\OCA\Encryption\Helper::registerFilesystemHooks();
|
||||
|
||||
// clear and register hooks
|
||||
\OC_FileProxy::clearProxies();
|
||||
\OC_FileProxy::register(new OCA\Encryption\Proxy());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,6 +153,41 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
* @brief test detection of encrypted files
|
||||
*/
|
||||
function testIsEncryptedPath() {
|
||||
|
||||
$util = new Encryption\Util($this->view, $this->userId);
|
||||
|
||||
self::loginHelper($this->userId);
|
||||
|
||||
$unencryptedFile = '/tmpUnencrypted-' . time() . '.txt';
|
||||
$encryptedFile = '/tmpEncrypted-' . time() . '.txt';
|
||||
|
||||
// Disable encryption proxy to write a unencrypted file
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$this->view->file_put_contents($this->userId . '/files/' . $unencryptedFile, $this->dataShort);
|
||||
|
||||
// Re-enable proxy - our work is done
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
// write a encrypted file
|
||||
$this->view->file_put_contents($this->userId . '/files/' . $encryptedFile, $this->dataShort);
|
||||
|
||||
// test if both files are detected correctly
|
||||
$this->assertFalse($util->isEncryptedPath($this->userId . '/files/' . $unencryptedFile));
|
||||
$this->assertTrue($util->isEncryptedPath($this->userId . '/files/' . $encryptedFile));
|
||||
|
||||
// cleanup
|
||||
$this->view->unlink($this->userId . '/files/' . $unencryptedFile, $this->dataShort);
|
||||
$this->view->unlink($this->userId . '/files/' . $encryptedFile, $this->dataShort);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
* @brief test setup of encryption directories
|
||||
@@ -219,7 +275,7 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
\OC_User::setUserId(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1);
|
||||
|
||||
$filename = '/tmp-' . time() . '.test';
|
||||
$filename = '/tmp-' . uniqid() . '.test';
|
||||
|
||||
// Disable encryption proxy to prevent recursive calls
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
@@ -247,7 +303,7 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
function testGetFileSize() {
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1);
|
||||
|
||||
$filename = 'tmp-' . time();
|
||||
$filename = 'tmp-' . uniqid();
|
||||
$externalFilename = '/' . $this->userId . '/files/' . $filename;
|
||||
|
||||
// Test for 0 byte files
|
||||
@@ -283,7 +339,7 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
function testEncryptAll() {
|
||||
|
||||
$filename = "/encryptAll" . time() . ".txt";
|
||||
$filename = "/encryptAll" . uniqid() . ".txt";
|
||||
$util = new Encryption\Util($this->view, $this->userId);
|
||||
|
||||
// disable encryption to upload a unencrypted file
|
||||
@@ -315,7 +371,7 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
function testDecryptAll() {
|
||||
|
||||
$filename = "/decryptAll" . time() . ".txt";
|
||||
$filename = "/decryptAll" . uniqid() . ".txt";
|
||||
$util = new Encryption\Util($this->view, $this->userId);
|
||||
|
||||
$this->view->file_put_contents($this->userId . '/files/' . $filename, $this->dataShort);
|
||||
@@ -323,9 +379,12 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
$fileInfoEncrypted = $this->view->getFileInfo($this->userId . '/files/' . $filename);
|
||||
|
||||
$this->assertTrue(is_array($fileInfoEncrypted));
|
||||
$this->assertEquals($fileInfoEncrypted['encrypted'], 1);
|
||||
|
||||
// encrypt all unencrypted files
|
||||
$util->decryptAll('/' . $this->userId . '/' . 'files');
|
||||
// decrypt all encrypted files
|
||||
$result = $util->decryptAll('/' . $this->userId . '/' . 'files');
|
||||
|
||||
$this->assertTrue($result);
|
||||
|
||||
$fileInfoUnencrypted = $this->view->getFileInfo($this->userId . '/files/' . $filename);
|
||||
|
||||
@@ -334,11 +393,133 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
// check if mtime and etags unchanged
|
||||
$this->assertEquals($fileInfoEncrypted['mtime'], $fileInfoUnencrypted['mtime']);
|
||||
$this->assertEquals($fileInfoEncrypted['etag'], $fileInfoUnencrypted['etag']);
|
||||
// file should no longer be encrypted
|
||||
$this->assertEquals(0, $fileInfoUnencrypted['encrypted']);
|
||||
|
||||
$this->view->unlink($this->userId . '/files/' . $filename);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* test if all keys get moved to the backup folder correctly
|
||||
*/
|
||||
function testBackupAllKeys() {
|
||||
self::loginHelper(self::TEST_ENCRYPTION_UTIL_USER1);
|
||||
|
||||
// create some dummy key files
|
||||
$encPath = '/' . self::TEST_ENCRYPTION_UTIL_USER1 . '/files_encryption';
|
||||
$this->view->file_put_contents($encPath . '/keyfiles/foo.key', 'key');
|
||||
$this->view->file_put_contents($encPath . '/share-keys/foo.user1.shareKey', 'share key');
|
||||
$this->view->mkdir($encPath . '/keyfiles/subfolder/');
|
||||
$this->view->mkdir($encPath . '/share-keys/subfolder/');
|
||||
$this->view->file_put_contents($encPath . '/keyfiles/subfolder/foo.key', 'key');
|
||||
$this->view->file_put_contents($encPath . '/share-keys/subfolder/foo.user1.shareKey', 'share key');
|
||||
|
||||
|
||||
$util = new \OCA\Encryption\Util($this->view, self::TEST_ENCRYPTION_UTIL_USER1);
|
||||
|
||||
$util->backupAllKeys('testing');
|
||||
|
||||
$encFolderContent = $this->view->getDirectoryContent($encPath);
|
||||
|
||||
$backupPath = '';
|
||||
foreach ($encFolderContent as $c) {
|
||||
$name = $c['name'];
|
||||
if (substr($name, 0, strlen('backup')) === 'backup') {
|
||||
$backupPath = $encPath . '/'. $c['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertTrue($backupPath !== '');
|
||||
|
||||
// check backupDir Content
|
||||
$this->assertTrue($this->view->is_dir($backupPath . '/keyfiles'));
|
||||
$this->assertTrue($this->view->is_dir($backupPath . '/share-keys'));
|
||||
$this->assertTrue($this->view->file_exists($backupPath . '/keyfiles/foo.key'));
|
||||
$this->assertTrue($this->view->file_exists($backupPath . '/share-keys/foo.user1.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists($backupPath . '/keyfiles/subfolder/foo.key'));
|
||||
$this->assertTrue($this->view->file_exists($backupPath . '/share-keys/subfolder/foo.user1.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists($backupPath . '/' . self::TEST_ENCRYPTION_UTIL_USER1 . '.private.key'));
|
||||
$this->assertTrue($this->view->file_exists($backupPath . '/' . self::TEST_ENCRYPTION_UTIL_USER1 . '.public.key'));
|
||||
|
||||
//cleanup
|
||||
$this->view->deleteAll($backupPath);
|
||||
$this->view->unlink($encPath . '/keyfiles/foo.key', 'key');
|
||||
$this->view->unlink($encPath . '/share-keys/foo.user1.shareKey', 'share key');
|
||||
}
|
||||
|
||||
|
||||
function testDescryptAllWithBrokenFiles() {
|
||||
|
||||
$file1 = "/decryptAll1" . uniqid() . ".txt";
|
||||
$file2 = "/decryptAll2" . uniqid() . ".txt";
|
||||
|
||||
$util = new Encryption\Util($this->view, $this->userId);
|
||||
|
||||
$this->view->file_put_contents($this->userId . '/files/' . $file1, $this->dataShort);
|
||||
$this->view->file_put_contents($this->userId . '/files/' . $file2, $this->dataShort);
|
||||
|
||||
$fileInfoEncrypted1 = $this->view->getFileInfo($this->userId . '/files/' . $file1);
|
||||
$fileInfoEncrypted2 = $this->view->getFileInfo($this->userId . '/files/' . $file2);
|
||||
|
||||
$this->assertTrue(is_array($fileInfoEncrypted1));
|
||||
$this->assertTrue(is_array($fileInfoEncrypted2));
|
||||
$this->assertEquals($fileInfoEncrypted1['encrypted'], 1);
|
||||
$this->assertEquals($fileInfoEncrypted2['encrypted'], 1);
|
||||
|
||||
// rename keyfile for file1 so that the decryption for file1 fails
|
||||
// Expected behaviour: decryptAll() returns false, file2 gets decrypted anyway
|
||||
$this->view->rename($this->userId . '/files_encryption/keyfiles/' . $file1 . '.key',
|
||||
$this->userId . '/files_encryption/keyfiles/' . $file1 . '.key.moved');
|
||||
|
||||
// decrypt all encrypted files
|
||||
$result = $util->decryptAll('/' . $this->userId . '/' . 'files');
|
||||
|
||||
$this->assertFalse($result);
|
||||
|
||||
$fileInfoUnencrypted1 = $this->view->getFileInfo($this->userId . '/files/' . $file1);
|
||||
$fileInfoUnencrypted2 = $this->view->getFileInfo($this->userId . '/files/' . $file2);
|
||||
|
||||
$this->assertTrue(is_array($fileInfoUnencrypted1));
|
||||
$this->assertTrue(is_array($fileInfoUnencrypted2));
|
||||
|
||||
// file1 should be still encrypted; file2 should be decrypted
|
||||
$this->assertEquals(1, $fileInfoUnencrypted1['encrypted']);
|
||||
$this->assertEquals(0, $fileInfoUnencrypted2['encrypted']);
|
||||
|
||||
// keyfiles and share keys should still exist
|
||||
$this->assertTrue($this->view->is_dir($this->userId . '/files_encryption/keyfiles/'));
|
||||
$this->assertTrue($this->view->is_dir($this->userId . '/files_encryption/share-keys/'));
|
||||
|
||||
// rename the keyfile for file1 back
|
||||
$this->view->rename($this->userId . '/files_encryption/keyfiles/' . $file1 . '.key.moved',
|
||||
$this->userId . '/files_encryption/keyfiles/' . $file1 . '.key');
|
||||
|
||||
// try again to decrypt all encrypted files
|
||||
$result = $util->decryptAll('/' . $this->userId . '/' . 'files');
|
||||
|
||||
$this->assertTrue($result);
|
||||
|
||||
$fileInfoUnencrypted1 = $this->view->getFileInfo($this->userId . '/files/' . $file1);
|
||||
$fileInfoUnencrypted2 = $this->view->getFileInfo($this->userId . '/files/' . $file2);
|
||||
|
||||
$this->assertTrue(is_array($fileInfoUnencrypted1));
|
||||
$this->assertTrue(is_array($fileInfoUnencrypted2));
|
||||
|
||||
// now both files should be decrypted
|
||||
$this->assertEquals(0, $fileInfoUnencrypted1['encrypted']);
|
||||
$this->assertEquals(0, $fileInfoUnencrypted2['encrypted']);
|
||||
|
||||
// keyfiles and share keys should be deleted
|
||||
$this->assertFalse($this->view->is_dir($this->userId . '/files_encryption/keyfiles/'));
|
||||
$this->assertFalse($this->view->is_dir($this->userId . '/files_encryption/share-keys/'));
|
||||
|
||||
$this->view->unlink($this->userId . '/files/' . $file1);
|
||||
$this->view->unlink($this->userId . '/files/' . $file2);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @large
|
||||
*/
|
||||
@@ -392,7 +573,30 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $user
|
||||
* @dataProvider dataProviderFortestIsMountPointApplicableToUser
|
||||
*/
|
||||
function testIsMountPointApplicableToUser($mount, $expectedResult) {
|
||||
self::loginHelper(self::TEST_ENCRYPTION_UTIL_USER1);
|
||||
$dummyClass = new DummyUtilClass($this->view, self::TEST_ENCRYPTION_UTIL_USER1);
|
||||
$result = $dummyClass->testIsMountPointApplicableToUser($mount);
|
||||
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
function dataProviderFortestIsMountPointApplicableToUser() {
|
||||
return array(
|
||||
array(array('applicable' => array('groups' => array(), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER1))), true),
|
||||
array(array('applicable' => array('groups' => array(), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2))), false),
|
||||
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP1), 'users' => array())), true),
|
||||
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP1), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2))), true),
|
||||
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP2), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2))), false),
|
||||
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP2), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2, 'all'))), true),
|
||||
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP2), 'users' => array('all'))), true),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param bool $create
|
||||
* @param bool $password
|
||||
*/
|
||||
@@ -416,6 +620,12 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
OCA\Encryption\Hooks::login($params);
|
||||
}
|
||||
|
||||
public static function logoutHelper() {
|
||||
\OC_Util::tearDownFS();
|
||||
\OC_User::setUserId('');
|
||||
\OC\Files\Filesystem::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function to set migration status to the right value
|
||||
* to be able to test the migration path
|
||||
@@ -440,3 +650,12 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* dummy class extends \OCA\Encryption\Util to access protected methods for testing
|
||||
*/
|
||||
class DummyUtilClass extends \OCA\Encryption\Util {
|
||||
public function testIsMountPointApplicableToUser($mount) {
|
||||
return $this->isMountPointApplicableToUser($mount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase {
|
||||
public $dataShort;
|
||||
public $stateFilesTrashbin;
|
||||
|
||||
private $storage;
|
||||
|
||||
public static function setUpBeforeClass() {
|
||||
// reset backend
|
||||
\OC_User::clearBackends();
|
||||
@@ -77,8 +79,8 @@ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase {
|
||||
$this->pass = \Test_Encryption_Webdav::TEST_ENCRYPTION_WEBDAV_USER1;
|
||||
|
||||
// init filesystem view
|
||||
$this->view = new \OC_FilesystemView('/');
|
||||
|
||||
$this->view = new \OC\Files\View('/');
|
||||
list($this->storage, $intPath) = $this->view->resolvePath('/');
|
||||
// init short data
|
||||
$this->dataShort = 'hats';
|
||||
|
||||
@@ -113,7 +115,7 @@ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase {
|
||||
function testWebdavPUT() {
|
||||
|
||||
// generate filename
|
||||
$filename = '/tmp-' . time() . '.txt';
|
||||
$filename = '/tmp-' . uniqid() . '.txt';
|
||||
|
||||
// set server vars
|
||||
$_SERVER['REQUEST_METHOD'] = 'OPTIONS';
|
||||
@@ -196,6 +198,9 @@ class Test_Encryption_Webdav extends \PHPUnit_Framework_TestCase {
|
||||
$_SERVER['HTTP_AUTHORIZATION'] = 'Basic dGVzdC13ZWJkYXYtdXNlcjE6dGVzdC13ZWJkYXYtdXNlcjE=';
|
||||
$_SERVER['PATH_INFO'] = '/webdav' . $filename;
|
||||
|
||||
// at the beginning the file should exist
|
||||
$this->assertTrue($this->view->file_exists('/' . $this->userId . '/files' . $filename));
|
||||
|
||||
// handle webdav request
|
||||
$content = $this->handleWebdavRequest();
|
||||
|
||||
|
||||
@@ -72,8 +72,16 @@ class Dropbox_OAuth_Curl extends Dropbox_OAuth {
|
||||
if (strtoupper($method) == 'POST') {
|
||||
curl_setopt($ch, CURLOPT_URL, $uri);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
// if (is_array($arguments))
|
||||
// $arguments=http_build_query($arguments);
|
||||
|
||||
//if (is_array($arguments))
|
||||
// $arguments=http_build_query($arguments);
|
||||
if(is_array($arguments)) {
|
||||
foreach ($arguments as $key => $value) {
|
||||
if ($value[0] === '@') {
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $arguments);
|
||||
// $httpHeaders['Content-Length']=strlen($arguments);
|
||||
} else {
|
||||
|
||||
2
apps/files_external/3rdparty/select2/.gitignore
vendored
Normal file
2
apps/files_external/3rdparty/select2/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.idea
|
||||
|
||||
18
apps/files_external/3rdparty/select2/LICENSE
vendored
Normal file
18
apps/files_external/3rdparty/select2/LICENSE
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
Copyright 2014 Igor Vaynberg
|
||||
|
||||
Version: @@ver@@ Timestamp: @@timestamp@@
|
||||
|
||||
This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
|
||||
General Public License version 2 (the "GPL License"). You may choose either license to govern your
|
||||
use of this software only upon the condition that you accept all of the terms of either the Apache
|
||||
License or the GPL License.
|
||||
|
||||
You may obtain a copy of the Apache License and the GPL License at:
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the Apache License
|
||||
or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
either express or implied. See the Apache License and the GPL License for the specific language governing
|
||||
permissions and limitations under the Apache License and the GPL License.
|
||||
90
apps/files_external/3rdparty/select2/README.md
vendored
Normal file
90
apps/files_external/3rdparty/select2/README.md
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
Select2
|
||||
=======
|
||||
|
||||
Select2 is a jQuery-based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.
|
||||
|
||||
To get started, checkout examples and documentation at http://ivaynberg.github.com/select2
|
||||
|
||||
Use cases
|
||||
---------
|
||||
|
||||
* Enhancing native selects with search.
|
||||
* Enhancing native selects with a better multi-select interface.
|
||||
* Loading data from JavaScript: easily load items via ajax and have them searchable.
|
||||
* Nesting optgroups: native selects only support one level of nested. Select2 does not have this restriction.
|
||||
* Tagging: ability to add new items on the fly.
|
||||
* Working with large, remote datasets: ability to partially load a dataset based on the search term.
|
||||
* Paging of large datasets: easy support for loading more pages when the results are scrolled to the end.
|
||||
* Templating: support for custom rendering of results and selections.
|
||||
|
||||
Browser compatibility
|
||||
---------------------
|
||||
* IE 8+
|
||||
* Chrome 8+
|
||||
* Firefox 10+
|
||||
* Safari 3+
|
||||
* Opera 10.6+
|
||||
|
||||
Usage
|
||||
-----
|
||||
You can source Select2 directly from a [CDN like JSDliver](http://www.jsdelivr.com/#!select2), [download it from this GitHub repo](https://github.com/ivaynberg/select2/tags), or use one of the integrations below.
|
||||
|
||||
Integrations
|
||||
------------
|
||||
|
||||
* [Wicket-Select2](https://github.com/ivaynberg/wicket-select2) (Java / [Apache Wicket](http://wicket.apache.org))
|
||||
* [select2-rails](https://github.com/argerim/select2-rails) (Ruby on Rails)
|
||||
* [AngularUI](http://angular-ui.github.com/#directives-select2) ([AngularJS](angularjs.org))
|
||||
* [Django](https://github.com/applegrew/django-select2)
|
||||
* [Symfony](https://github.com/19Gerhard85/sfSelect2WidgetsPlugin)
|
||||
* [Symfony2](https://github.com/avocode/FormExtensions)
|
||||
* [Bootstrap 2](https://github.com/t0m/select2-bootstrap-css) and [Bootstrap 3](https://github.com/t0m/select2-bootstrap-css/tree/bootstrap3) (CSS skins)
|
||||
* [Meteor](https://github.com/nate-strauser/meteor-select2) (modern reactive JavaScript framework; + [Bootstrap 3 skin](https://github.com/esperadomedia/meteor-select2-bootstrap3-css/))
|
||||
* [Yii 2.x](http://demos.krajee.com/widgets#select2)
|
||||
* [Yii 1.x](https://github.com/tonybolzan/yii-select2)
|
||||
|
||||
Internationalization (i18n)
|
||||
---------------------------
|
||||
|
||||
Select2 supports multiple languages by simply including the right
|
||||
language JS file (`select2_locale_it.js`, `select2_locale_nl.js`, etc.).
|
||||
|
||||
Missing a language? Just copy `select2_locale_en.js.template`, translate
|
||||
it, and make a pull request back to Select2 here on GitHub.
|
||||
|
||||
Bug tracker
|
||||
-----------
|
||||
|
||||
Have a bug? Please create an issue here on GitHub!
|
||||
|
||||
https://github.com/ivaynberg/select2/issues
|
||||
|
||||
Mailing list
|
||||
------------
|
||||
|
||||
Have a question? Ask on our mailing list!
|
||||
|
||||
select2@googlegroups.com
|
||||
|
||||
https://groups.google.com/d/forum/select2
|
||||
|
||||
|
||||
Copyright and license
|
||||
---------------------
|
||||
|
||||
Copyright 2012 Igor Vaynberg
|
||||
|
||||
This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
|
||||
General Public License version 2 (the "GPL License"). You may choose either license to govern your
|
||||
use of this software only upon the condition that you accept all of the terms of either the Apache
|
||||
License or the GPL License.
|
||||
|
||||
You may obtain a copy of the Apache License and the GPL License in the LICENSE file, or at:
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the Apache License
|
||||
or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
either express or implied. See the Apache License and the GPL License for the specific language governing
|
||||
permissions and limitations under the Apache License and the GPL License.
|
||||
8
apps/files_external/3rdparty/select2/bower.json
vendored
Normal file
8
apps/files_external/3rdparty/select2/bower.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "select2",
|
||||
"version": "3.4.8",
|
||||
"main": ["select2.js", "select2.css", "select2.png", "select2x2.png", "select2-spinner.gif"],
|
||||
"dependencies": {
|
||||
"jquery": ">= 1.7.1"
|
||||
}
|
||||
}
|
||||
66
apps/files_external/3rdparty/select2/component.json
vendored
Normal file
66
apps/files_external/3rdparty/select2/component.json
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "select2",
|
||||
"repo": "ivaynberg/select2",
|
||||
"description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
|
||||
"version": "3.4.8",
|
||||
"demo": "http://ivaynberg.github.io/select2/",
|
||||
"keywords": [
|
||||
"jquery"
|
||||
],
|
||||
"main": "select2.js",
|
||||
"styles": [
|
||||
"select2.css",
|
||||
"select2-bootstrap.css"
|
||||
],
|
||||
"scripts": [
|
||||
"select2.js",
|
||||
"select2_locale_ar.js",
|
||||
"select2_locale_bg.js",
|
||||
"select2_locale_ca.js",
|
||||
"select2_locale_cs.js",
|
||||
"select2_locale_da.js",
|
||||
"select2_locale_de.js",
|
||||
"select2_locale_el.js",
|
||||
"select2_locale_es.js",
|
||||
"select2_locale_et.js",
|
||||
"select2_locale_eu.js",
|
||||
"select2_locale_fa.js",
|
||||
"select2_locale_fi.js",
|
||||
"select2_locale_fr.js",
|
||||
"select2_locale_gl.js",
|
||||
"select2_locale_he.js",
|
||||
"select2_locale_hr.js",
|
||||
"select2_locale_hu.js",
|
||||
"select2_locale_id.js",
|
||||
"select2_locale_is.js",
|
||||
"select2_locale_it.js",
|
||||
"select2_locale_ja.js",
|
||||
"select2_locale_ka.js",
|
||||
"select2_locale_ko.js",
|
||||
"select2_locale_lt.js",
|
||||
"select2_locale_lv.js",
|
||||
"select2_locale_mk.js",
|
||||
"select2_locale_ms.js",
|
||||
"select2_locale_nl.js",
|
||||
"select2_locale_no.js",
|
||||
"select2_locale_pl.js",
|
||||
"select2_locale_pt-BR.js",
|
||||
"select2_locale_pt-PT.js",
|
||||
"select2_locale_ro.js",
|
||||
"select2_locale_ru.js",
|
||||
"select2_locale_sk.js",
|
||||
"select2_locale_sv.js",
|
||||
"select2_locale_th.js",
|
||||
"select2_locale_tr.js",
|
||||
"select2_locale_uk.js",
|
||||
"select2_locale_vi.js",
|
||||
"select2_locale_zh-CN.js",
|
||||
"select2_locale_zh-TW.js"
|
||||
],
|
||||
"images": [
|
||||
"select2-spinner.gif",
|
||||
"select2.png",
|
||||
"select2x2.png"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
29
apps/files_external/3rdparty/select2/composer.json
vendored
Normal file
29
apps/files_external/3rdparty/select2/composer.json
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name":
|
||||
"ivaynberg/select2",
|
||||
"description": "Select2 is a jQuery based replacement for select boxes.",
|
||||
"version": "3.4.8",
|
||||
"type": "component",
|
||||
"homepage": "http://ivaynberg.github.io/select2/",
|
||||
"license": "Apache-2.0",
|
||||
"require": {
|
||||
"robloach/component-installer": "*",
|
||||
"components/jquery": ">=1.7.1"
|
||||
},
|
||||
"extra": {
|
||||
"component": {
|
||||
"scripts": [
|
||||
"select2.js"
|
||||
],
|
||||
"files": [
|
||||
"select2.js",
|
||||
"select2_locale_*.js",
|
||||
"select2.css",
|
||||
"select2-bootstrap.css",
|
||||
"select2-spinner.gif",
|
||||
"select2.png",
|
||||
"select2x2.png"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
20
apps/files_external/3rdparty/select2/package.json
vendored
Normal file
20
apps/files_external/3rdparty/select2/package.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name" : "Select2",
|
||||
"description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
|
||||
"homepage": "http://ivaynberg.github.io/select2",
|
||||
"author": "Igor Vaynberg",
|
||||
"repository": {"type": "git", "url": "git://github.com/ivaynberg/select2.git"},
|
||||
"main": "select2.js",
|
||||
"version": "3.4.8",
|
||||
"jspm": {
|
||||
"main": "select2",
|
||||
"files": ["select2.js", "select2.png", "select2.css", "select2-spinner.gif"],
|
||||
"shim": {
|
||||
"select2": {
|
||||
"imports": ["jquery", "./select2.css!"],
|
||||
"exports": "$"
|
||||
}
|
||||
},
|
||||
"buildConfig": { "uglify": true }
|
||||
}
|
||||
}
|
||||
79
apps/files_external/3rdparty/select2/release.sh
vendored
Executable file
79
apps/files_external/3rdparty/select2/release.sh
vendored
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo -n "Enter the version for this release: "
|
||||
|
||||
read ver
|
||||
|
||||
if [ ! $ver ]; then
|
||||
echo "Invalid version."
|
||||
exit
|
||||
fi
|
||||
|
||||
name="select2"
|
||||
js="$name.js"
|
||||
mini="$name.min.js"
|
||||
css="$name.css"
|
||||
release="$name-$ver"
|
||||
tag="$ver"
|
||||
branch="build-$ver"
|
||||
curbranch=`git branch | grep "*" | sed "s/* //"`
|
||||
timestamp=$(date)
|
||||
tokens="s/@@ver@@/$ver/g;s/\@@timestamp@@/$timestamp/g"
|
||||
remote="github"
|
||||
|
||||
echo "Pulling from origin"
|
||||
|
||||
git pull
|
||||
|
||||
echo "Updating Version Identifiers"
|
||||
|
||||
sed -E -e "s/\"version\": \"([0-9\.]+)\",/\"version\": \"$ver\",/g" -i -- bower.json select2.jquery.json component.json composer.json package.json
|
||||
|
||||
git add bower.json
|
||||
git add select2.jquery.json
|
||||
git add component.json
|
||||
git add composer.json
|
||||
git add package.json
|
||||
|
||||
git commit -m "modified version identifiers in descriptors for release $ver"
|
||||
git push
|
||||
|
||||
git branch "$branch"
|
||||
git checkout "$branch"
|
||||
|
||||
echo "Tokenizing..."
|
||||
|
||||
find . -name "$js" | xargs -I{} sed -e "$tokens" -i -- {}
|
||||
find . -name "$css" | xargs -I{} sed -e "$tokens" -i -- {}
|
||||
|
||||
sed -e "s/latest/$ver/g" -i -- bower.json
|
||||
|
||||
git add "$js"
|
||||
git add "$css"
|
||||
|
||||
echo "Minifying..."
|
||||
|
||||
echo "/*" > "$mini"
|
||||
cat LICENSE | sed "$tokens" >> "$mini"
|
||||
echo "*/" >> "$mini"
|
||||
|
||||
curl -s \
|
||||
--data-urlencode "js_code@$js" \
|
||||
http://marijnhaverbeke.nl/uglifyjs \
|
||||
>> "$mini"
|
||||
|
||||
git add "$mini"
|
||||
|
||||
git commit -m "release $ver"
|
||||
|
||||
echo "Tagging..."
|
||||
git tag -a "$tag" -m "tagged version $ver"
|
||||
git push "$remote" --tags
|
||||
|
||||
echo "Cleaning Up..."
|
||||
|
||||
git checkout "$curbranch"
|
||||
git branch -D "$branch"
|
||||
|
||||
echo "Done"
|
||||
87
apps/files_external/3rdparty/select2/select2-bootstrap.css
vendored
Normal file
87
apps/files_external/3rdparty/select2/select2-bootstrap.css
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
.form-control .select2-choice {
|
||||
border: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.form-control .select2-choice .select2-arrow {
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
.form-control.select2-container {
|
||||
height: auto !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-control.select2-container.select2-dropdown-open {
|
||||
border-color: #5897FB;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.form-control .select2-container.select2-dropdown-open .select2-choices {
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.form-control.select2-container .select2-choices {
|
||||
border: 0 !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.control-group.warning .select2-container .select2-choice,
|
||||
.control-group.warning .select2-container .select2-choices,
|
||||
.control-group.warning .select2-container-active .select2-choice,
|
||||
.control-group.warning .select2-container-active .select2-choices,
|
||||
.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choices,
|
||||
.control-group.warning .select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #C09853 !important;
|
||||
}
|
||||
|
||||
.control-group.warning .select2-container .select2-choice div {
|
||||
border-left: 1px solid #C09853 !important;
|
||||
background: #FCF8E3 !important;
|
||||
}
|
||||
|
||||
.control-group.error .select2-container .select2-choice,
|
||||
.control-group.error .select2-container .select2-choices,
|
||||
.control-group.error .select2-container-active .select2-choice,
|
||||
.control-group.error .select2-container-active .select2-choices,
|
||||
.control-group.error .select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.control-group.error .select2-dropdown-open.select2-drop-above .select2-choices,
|
||||
.control-group.error .select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #B94A48 !important;
|
||||
}
|
||||
|
||||
.control-group.error .select2-container .select2-choice div {
|
||||
border-left: 1px solid #B94A48 !important;
|
||||
background: #F2DEDE !important;
|
||||
}
|
||||
|
||||
.control-group.info .select2-container .select2-choice,
|
||||
.control-group.info .select2-container .select2-choices,
|
||||
.control-group.info .select2-container-active .select2-choice,
|
||||
.control-group.info .select2-container-active .select2-choices,
|
||||
.control-group.info .select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.control-group.info .select2-dropdown-open.select2-drop-above .select2-choices,
|
||||
.control-group.info .select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #3A87AD !important;
|
||||
}
|
||||
|
||||
.control-group.info .select2-container .select2-choice div {
|
||||
border-left: 1px solid #3A87AD !important;
|
||||
background: #D9EDF7 !important;
|
||||
}
|
||||
|
||||
.control-group.success .select2-container .select2-choice,
|
||||
.control-group.success .select2-container .select2-choices,
|
||||
.control-group.success .select2-container-active .select2-choice,
|
||||
.control-group.success .select2-container-active .select2-choices,
|
||||
.control-group.success .select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.control-group.success .select2-dropdown-open.select2-drop-above .select2-choices,
|
||||
.control-group.success .select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #468847 !important;
|
||||
}
|
||||
|
||||
.control-group.success .select2-container .select2-choice div {
|
||||
border-left: 1px solid #468847 !important;
|
||||
background: #DFF0D8 !important;
|
||||
}
|
||||
BIN
apps/files_external/3rdparty/select2/select2-spinner.gif
vendored
Normal file
BIN
apps/files_external/3rdparty/select2/select2-spinner.gif
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
646
apps/files_external/3rdparty/select2/select2.css
vendored
Normal file
646
apps/files_external/3rdparty/select2/select2.css
vendored
Normal file
@@ -0,0 +1,646 @@
|
||||
/*
|
||||
Version: 3.4.8 Timestamp: Thu May 1 09:50:32 EDT 2014
|
||||
*/
|
||||
.select2-container {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
/* inline-block for ie7 */
|
||||
zoom: 1;
|
||||
*display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.select2-container,
|
||||
.select2-drop,
|
||||
.select2-search,
|
||||
.select2-search input {
|
||||
/*
|
||||
Force border-box so that % widths fit the parent
|
||||
container without overlap because of margin/padding.
|
||||
More Info : http://www.quirksmode.org/css/box.html
|
||||
*/
|
||||
-webkit-box-sizing: border-box; /* webkit */
|
||||
-moz-box-sizing: border-box; /* firefox */
|
||||
box-sizing: border-box; /* css3 */
|
||||
}
|
||||
|
||||
.select2-container .select2-choice {
|
||||
display: block;
|
||||
height: 26px;
|
||||
padding: 0 0 0 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
white-space: nowrap;
|
||||
line-height: 26px;
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
background-color: #fff;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff));
|
||||
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%);
|
||||
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
|
||||
background-image: linear-gradient(to top, #eee 0%, #fff 50%);
|
||||
}
|
||||
|
||||
.select2-container.select2-drop-above .select2-choice {
|
||||
border-bottom-color: #aaa;
|
||||
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff));
|
||||
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%);
|
||||
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);
|
||||
background-image: linear-gradient(to bottom, #eee 0%, #fff 90%);
|
||||
}
|
||||
|
||||
.select2-container.select2-allowclear .select2-choice .select2-chosen {
|
||||
margin-right: 42px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice > .select2-chosen {
|
||||
margin-right: 26px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
float: none;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice abbr {
|
||||
display: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
top: 8px;
|
||||
|
||||
font-size: 1px;
|
||||
text-decoration: none;
|
||||
|
||||
border: 0;
|
||||
background: url('select2.png') right top no-repeat;
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.select2-container.select2-allowclear .select2-choice abbr {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice abbr:hover {
|
||||
background-position: right -11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select2-drop-mask {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
opacity: 0;
|
||||
z-index: 9998;
|
||||
/* styles required for IE to work */
|
||||
background-color: #fff;
|
||||
filter: alpha(opacity=0);
|
||||
}
|
||||
|
||||
.select2-drop {
|
||||
width: 100%;
|
||||
margin-top: -1px;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
top: 100%;
|
||||
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border: 1px solid #aaa;
|
||||
border-top: 0;
|
||||
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
||||
-webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above {
|
||||
margin-top: 1px;
|
||||
border-top: 1px solid #aaa;
|
||||
border-bottom: 0;
|
||||
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
||||
-webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.select2-drop-active {
|
||||
border: 1px solid #5897fb;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above.select2-drop-active {
|
||||
border-top: 1px solid #5897fb;
|
||||
}
|
||||
|
||||
.select2-drop-auto-width {
|
||||
border-top: 1px solid #aaa;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.select2-drop-auto-width .select2-search {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice .select2-arrow {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
border-left: 1px solid #aaa;
|
||||
border-radius: 0 4px 4px 0;
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
background: #ccc;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
|
||||
background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||
background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
|
||||
background-image: linear-gradient(to top, #ccc 0%, #eee 60%);
|
||||
}
|
||||
|
||||
.select2-container .select2-choice .select2-arrow b {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url('select2.png') no-repeat 0 1px;
|
||||
}
|
||||
|
||||
.select2-search {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
min-height: 26px;
|
||||
margin: 0;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
|
||||
position: relative;
|
||||
z-index: 10000;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
width: 100%;
|
||||
height: auto !important;
|
||||
min-height: 26px;
|
||||
padding: 4px 20px 4px 5px;
|
||||
margin: 0;
|
||||
|
||||
outline: 0;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 0;
|
||||
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
|
||||
background: #fff url('select2.png') no-repeat 100% -22px;
|
||||
background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
|
||||
background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above .select2-search input {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.select2-search input.select2-active {
|
||||
background: #fff url('select2-spinner.gif') no-repeat 100%;
|
||||
background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
|
||||
background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
|
||||
}
|
||||
|
||||
.select2-container-active .select2-choice,
|
||||
.select2-container-active .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
outline: none;
|
||||
|
||||
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice {
|
||||
border-bottom-color: transparent;
|
||||
-webkit-box-shadow: 0 1px 0 #fff inset;
|
||||
box-shadow: 0 1px 0 #fff inset;
|
||||
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
background-color: #eee;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee));
|
||||
background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%);
|
||||
background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
|
||||
background-image: linear-gradient(to top, #fff 0%, #eee 50%);
|
||||
}
|
||||
|
||||
.select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.select2-dropdown-open.select2-drop-above .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
border-top-color: transparent;
|
||||
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee));
|
||||
background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%);
|
||||
background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
|
||||
background-image: linear-gradient(to bottom, #fff 0%, #eee 50%);
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice .select2-arrow {
|
||||
background: transparent;
|
||||
border-left: none;
|
||||
filter: none;
|
||||
}
|
||||
.select2-dropdown-open .select2-choice .select2-arrow b {
|
||||
background-position: -18px 1px;
|
||||
}
|
||||
|
||||
.select2-hidden-accessible {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
/* results */
|
||||
.select2-results {
|
||||
max-height: 200px;
|
||||
padding: 0 0 0 4px;
|
||||
margin: 4px 4px 4px 0;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.select2-results ul.select2-result-sub {
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.select2-results li {
|
||||
list-style: none;
|
||||
display: list-item;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.select2-results li.select2-result-with-children > .select2-result-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.select2-results .select2-result-label {
|
||||
padding: 3px 7px 4px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
|
||||
min-height: 1em;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.select2-results-dept-1 .select2-result-label { padding-left: 20px }
|
||||
.select2-results-dept-2 .select2-result-label { padding-left: 40px }
|
||||
.select2-results-dept-3 .select2-result-label { padding-left: 60px }
|
||||
.select2-results-dept-4 .select2-result-label { padding-left: 80px }
|
||||
.select2-results-dept-5 .select2-result-label { padding-left: 100px }
|
||||
.select2-results-dept-6 .select2-result-label { padding-left: 110px }
|
||||
.select2-results-dept-7 .select2-result-label { padding-left: 120px }
|
||||
|
||||
.select2-results .select2-highlighted {
|
||||
background: #3875d7;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.select2-results li em {
|
||||
background: #feffde;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.select2-results .select2-highlighted em {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.select2-results .select2-highlighted ul {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
|
||||
.select2-results .select2-no-results,
|
||||
.select2-results .select2-searching,
|
||||
.select2-results .select2-selection-limit {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
/*
|
||||
disabled look for disabled choices in the results dropdown
|
||||
*/
|
||||
.select2-results .select2-disabled.select2-highlighted {
|
||||
color: #666;
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
cursor: default;
|
||||
}
|
||||
.select2-results .select2-disabled {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-results .select2-selected {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-more-results.select2-active {
|
||||
background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%;
|
||||
}
|
||||
|
||||
.select2-more-results {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* disabled styles */
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border: 1px solid #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice .select2-arrow {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice abbr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* multiselect */
|
||||
|
||||
.select2-container-multi .select2-choices {
|
||||
height: auto !important;
|
||||
height: 1%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
cursor: text;
|
||||
overflow: hidden;
|
||||
|
||||
background-color: #fff;
|
||||
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff));
|
||||
background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%);
|
||||
background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%);
|
||||
background-image: linear-gradient(to bottom, #eee 1%, #fff 15%);
|
||||
}
|
||||
|
||||
.select2-locked {
|
||||
padding: 3px 5px 3px 5px !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices {
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
outline: none;
|
||||
|
||||
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
}
|
||||
.select2-container-multi .select2-choices li {
|
||||
float: left;
|
||||
list-style: none;
|
||||
}
|
||||
html[dir="rtl"] .select2-container-multi .select2-choices li
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-field {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input {
|
||||
padding: 5px;
|
||||
margin: 1px 0;
|
||||
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
color: #666;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input.select2-active {
|
||||
background: #fff url('select2-spinner.gif') no-repeat 100% !important;
|
||||
}
|
||||
|
||||
.select2-default {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice {
|
||||
padding: 3px 5px 3px 18px;
|
||||
margin: 3px 0 3px 5px;
|
||||
position: relative;
|
||||
|
||||
line-height: 13px;
|
||||
color: #333;
|
||||
cursor: default;
|
||||
border: 1px solid #aaaaaa;
|
||||
|
||||
border-radius: 3px;
|
||||
|
||||
-webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
background-color: #e4e4e4;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0);
|
||||
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee));
|
||||
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
background-image: linear-gradient(to top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
}
|
||||
html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice
|
||||
{
|
||||
margin-left: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
|
||||
cursor: default;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice-focus {
|
||||
background: #d4d4d4;
|
||||
}
|
||||
|
||||
.select2-search-choice-close {
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 13px;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 4px;
|
||||
|
||||
font-size: 1px;
|
||||
outline: none;
|
||||
background: url('select2.png') right top no-repeat;
|
||||
}
|
||||
html[dir="rtl"] .select2-search-choice-close {
|
||||
right: auto;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-search-choice-close {
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
|
||||
background-position: right -11px;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
|
||||
background-position: right -11px;
|
||||
}
|
||||
|
||||
/* disabled styles */
|
||||
.select2-container-multi.select2-container-disabled .select2-choices {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border: 1px solid #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
|
||||
padding: 3px 5px 3px 5px;
|
||||
border: 1px solid #ddd;
|
||||
background-image: none;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none;
|
||||
background: none;
|
||||
}
|
||||
/* end multiselect */
|
||||
|
||||
|
||||
.select2-result-selectable .select2-match,
|
||||
.select2-result-unselectable .select2-match {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.select2-offscreen, .select2-offscreen:focus {
|
||||
clip: rect(0 0 0 0) !important;
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
border: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
position: absolute !important;
|
||||
outline: 0 !important;
|
||||
left: 0px !important;
|
||||
top: 0px !important;
|
||||
}
|
||||
|
||||
.select2-display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-measure-scrollbar {
|
||||
position: absolute;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
/* Retina-ize icons */
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) {
|
||||
.select2-search input,
|
||||
.select2-search-choice-close,
|
||||
.select2-container .select2-choice abbr,
|
||||
.select2-container .select2-choice .select2-arrow b {
|
||||
background-image: url('select2x2.png') !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: 60px 40px !important;
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
background-position: 100% -21px !important;
|
||||
}
|
||||
}
|
||||
36
apps/files_external/3rdparty/select2/select2.jquery.json
vendored
Normal file
36
apps/files_external/3rdparty/select2/select2.jquery.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "select2",
|
||||
"title": "Select2",
|
||||
"description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
|
||||
"keywords": [
|
||||
"select",
|
||||
"autocomplete",
|
||||
"typeahead",
|
||||
"dropdown",
|
||||
"multiselect",
|
||||
"tag",
|
||||
"tagging"
|
||||
],
|
||||
"version": "3.4.8",
|
||||
"author": {
|
||||
"name": "Igor Vaynberg",
|
||||
"url": "https://github.com/ivaynberg"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "Apache",
|
||||
"url": "http://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"type": "GPL v2",
|
||||
"url": "http://www.gnu.org/licenses/gpl-2.0.html"
|
||||
}
|
||||
],
|
||||
"bugs": "https://github.com/ivaynberg/select2/issues",
|
||||
"homepage": "http://ivaynberg.github.com/select2",
|
||||
"docs": "http://ivaynberg.github.com/select2/",
|
||||
"download": "https://github.com/ivaynberg/select2/tags",
|
||||
"dependencies": {
|
||||
"jquery": ">=1.7.1"
|
||||
}
|
||||
}
|
||||
3448
apps/files_external/3rdparty/select2/select2.js
vendored
Normal file
3448
apps/files_external/3rdparty/select2/select2.js
vendored
Normal file
File diff suppressed because one or more lines are too long
22
apps/files_external/3rdparty/select2/select2.min.js
vendored
Normal file
22
apps/files_external/3rdparty/select2/select2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
apps/files_external/3rdparty/select2/select2.png
vendored
Normal file
BIN
apps/files_external/3rdparty/select2/select2.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 613 B |
17
apps/files_external/3rdparty/select2/select2_locale_ar.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_ar.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Arabic translation.
|
||||
*
|
||||
* Author: Adel KEDJOUR <adel@kedjour.com>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "لم يتم العثور على مطابقات"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; if (n == 1){ return "الرجاء إدخال حرف واحد على الأكثر"; } return n == 2 ? "الرجاء إدخال حرفين على الأكثر" : "الرجاء إدخال " + n + " على الأكثر"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; if (n == 1){ return "الرجاء إدخال حرف واحد على الأقل"; } return n == 2 ? "الرجاء إدخال حرفين على الأقل" : "الرجاء إدخال " + n + " على الأقل "; },
|
||||
formatSelectionTooBig: function (limit) { if (n == 1){ return "يمكنك أن تختار إختيار واحد فقط"; } return n == 2 ? "يمكنك أن تختار إختيارين فقط" : "يمكنك أن تختار " + n + " إختيارات فقط"; },
|
||||
formatLoadMore: function (pageNumber) { return "تحميل المزيد من النتائج…"; },
|
||||
formatSearching: function () { return "البحث…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
18
apps/files_external/3rdparty/select2/select2_locale_bg.js
vendored
Normal file
18
apps/files_external/3rdparty/select2/select2_locale_bg.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Select2 Bulgarian translation.
|
||||
*
|
||||
* @author Lubomir Vikev <lubomirvikev@gmail.com>
|
||||
* @author Uriy Efremochkin <efremochkin@uriy.me>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Няма намерени съвпадения"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Моля въведете още " + n + " символ" + (n > 1 ? "а" : ""); },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Моля въведете с " + n + " по-малко символ" + (n > 1 ? "а" : ""); },
|
||||
formatSelectionTooBig: function (limit) { return "Можете да направите до " + limit + (limit > 1 ? " избора" : " избор"); },
|
||||
formatLoadMore: function (pageNumber) { return "Зареждат се още…"; },
|
||||
formatSearching: function () { return "Търсене…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
17
apps/files_external/3rdparty/select2/select2_locale_ca.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_ca.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Catalan translation.
|
||||
*
|
||||
* Author: David Planella <david.planella@gmail.com>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "No s'ha trobat cap coincidència"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Introduïu " + n + " caràcter" + (n == 1 ? "" : "s") + " més"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Introduïu " + n + " caràcter" + (n == 1? "" : "s") + "menys"; },
|
||||
formatSelectionTooBig: function (limit) { return "Només podeu seleccionar " + limit + " element" + (limit == 1 ? "" : "s"); },
|
||||
formatLoadMore: function (pageNumber) { return "S'estan carregant més resultats…"; },
|
||||
formatSearching: function () { return "S'està cercant…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
49
apps/files_external/3rdparty/select2/select2_locale_cs.js
vendored
Normal file
49
apps/files_external/3rdparty/select2/select2_locale_cs.js
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Select2 Czech translation.
|
||||
*
|
||||
* Author: Michal Marek <ahoj@michal-marek.cz>
|
||||
* Author - sklonovani: David Vallner <david@vallner.net>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
// use text for the numbers 2 through 4
|
||||
var smallNumbers = {
|
||||
2: function(masc) { return (masc ? "dva" : "dvě"); },
|
||||
3: function() { return "tři"; },
|
||||
4: function() { return "čtyři"; }
|
||||
}
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Nenalezeny žádné položky"; },
|
||||
formatInputTooShort: function (input, min) {
|
||||
var n = min - input.length;
|
||||
if (n == 1) {
|
||||
return "Prosím zadejte ještě jeden znak";
|
||||
} else if (n <= 4) {
|
||||
return "Prosím zadejte ještě další "+smallNumbers[n](true)+" znaky";
|
||||
} else {
|
||||
return "Prosím zadejte ještě dalších "+n+" znaků";
|
||||
}
|
||||
},
|
||||
formatInputTooLong: function (input, max) {
|
||||
var n = input.length - max;
|
||||
if (n == 1) {
|
||||
return "Prosím zadejte o jeden znak méně";
|
||||
} else if (n <= 4) {
|
||||
return "Prosím zadejte o "+smallNumbers[n](true)+" znaky méně";
|
||||
} else {
|
||||
return "Prosím zadejte o "+n+" znaků méně";
|
||||
}
|
||||
},
|
||||
formatSelectionTooBig: function (limit) {
|
||||
if (limit == 1) {
|
||||
return "Můžete zvolit jen jednu položku";
|
||||
} else if (limit <= 4) {
|
||||
return "Můžete zvolit maximálně "+smallNumbers[limit](false)+" položky";
|
||||
} else {
|
||||
return "Můžete zvolit maximálně "+limit+" položek";
|
||||
}
|
||||
},
|
||||
formatLoadMore: function (pageNumber) { return "Načítají se další výsledky…"; },
|
||||
formatSearching: function () { return "Vyhledávání…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
17
apps/files_external/3rdparty/select2/select2_locale_da.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_da.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Danish translation.
|
||||
*
|
||||
* Author: Anders Jenbo <anders@jenbo.dk>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Ingen resultater fundet"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Angiv venligst " + n + " tegn mere"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Angiv venligst " + n + " tegn mindre"; },
|
||||
formatSelectionTooBig: function (limit) { return "Du kan kun vælge " + limit + " emne" + (limit === 1 ? "" : "r"); },
|
||||
formatLoadMore: function (pageNumber) { return "Indlæser flere resultater…"; },
|
||||
formatSearching: function () { return "Søger…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
15
apps/files_external/3rdparty/select2/select2_locale_de.js
vendored
Normal file
15
apps/files_external/3rdparty/select2/select2_locale_de.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Select2 German translation
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Keine Übereinstimmungen gefunden"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Bitte " + n + " Zeichen mehr eingeben"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Bitte " + n + " Zeichen weniger eingeben"; },
|
||||
formatSelectionTooBig: function (limit) { return "Sie können nur " + limit + " Eintr" + (limit === 1 ? "ag" : "äge") + " auswählen"; },
|
||||
formatLoadMore: function (pageNumber) { return "Lade mehr Ergebnisse…"; },
|
||||
formatSearching: function () { return "Suche…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
17
apps/files_external/3rdparty/select2/select2_locale_el.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_el.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Greek translation.
|
||||
*
|
||||
* @author Uriy Efremochkin <efremochkin@uriy.me>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Δεν βρέθηκαν αποτελέσματα"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Παρακαλούμε εισάγετε " + n + " περισσότερο" + (n > 1 ? "υς" : "") + " χαρακτήρ" + (n > 1 ? "ες" : "α"); },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Παρακαλούμε διαγράψτε " + n + " χαρακτήρ" + (n > 1 ? "ες" : "α"); },
|
||||
formatSelectionTooBig: function (limit) { return "Μπορείτε να επιλέξετε μόνο " + limit + " αντικείμεν" + (limit > 1 ? "α" : "ο"); },
|
||||
formatLoadMore: function (pageNumber) { return "Φόρτωση περισσότερων…"; },
|
||||
formatSearching: function () { return "Αναζήτηση…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
18
apps/files_external/3rdparty/select2/select2_locale_en.js.template
vendored
Normal file
18
apps/files_external/3rdparty/select2/select2_locale_en.js.template
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Select2 <Language> translation.
|
||||
*
|
||||
* Author: Your Name <your@email>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatMatches: function (matches) { return matches + " results are available, use up and down arrow keys to navigate."; },
|
||||
formatNoMatches: function () { return "No matches found"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1 ? "" : "s"); },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1 ? "" : "s"); },
|
||||
formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
|
||||
formatLoadMore: function (pageNumber) { return "Loading more results…"; },
|
||||
formatSearching: function () { return "Searching…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
15
apps/files_external/3rdparty/select2/select2_locale_es.js
vendored
Normal file
15
apps/files_external/3rdparty/select2/select2_locale_es.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Select2 Spanish translation
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "No se encontraron resultados"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Por favor, introduzca " + n + " car" + (n == 1? "ácter" : "acteres"); },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Por favor, elimine " + n + " car" + (n == 1? "ácter" : "acteres"); },
|
||||
formatSelectionTooBig: function (limit) { return "Sólo puede seleccionar " + limit + " elemento" + (limit == 1 ? "" : "s"); },
|
||||
formatLoadMore: function (pageNumber) { return "Cargando más resultados…"; },
|
||||
formatSearching: function () { return "Buscando…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
17
apps/files_external/3rdparty/select2/select2_locale_et.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_et.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Estonian translation.
|
||||
*
|
||||
* Author: Kuldar Kalvik <kuldar@kalvik.ee>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Tulemused puuduvad"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Sisesta " + n + " täht" + (n == 1 ? "" : "e") + " rohkem"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Sisesta " + n + " täht" + (n == 1? "" : "e") + " vähem"; },
|
||||
formatSelectionTooBig: function (limit) { return "Saad vaid " + limit + " tulemus" + (limit == 1 ? "e" : "t") + " valida"; },
|
||||
formatLoadMore: function (pageNumber) { return "Laen tulemusi.."; },
|
||||
formatSearching: function () { return "Otsin.."; }
|
||||
});
|
||||
})(jQuery);
|
||||
43
apps/files_external/3rdparty/select2/select2_locale_eu.js
vendored
Normal file
43
apps/files_external/3rdparty/select2/select2_locale_eu.js
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Select2 Basque translation.
|
||||
*
|
||||
* Author: Julen Ruiz Aizpuru <julenx at gmail dot com>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () {
|
||||
return "Ez da bat datorrenik aurkitu";
|
||||
},
|
||||
formatInputTooShort: function (input, min) {
|
||||
var n = min - input.length;
|
||||
if (n === 1) {
|
||||
return "Idatzi karaktere bat gehiago";
|
||||
} else {
|
||||
return "Idatzi " + n + " karaktere gehiago";
|
||||
}
|
||||
},
|
||||
formatInputTooLong: function (input, max) {
|
||||
var n = input.length - max;
|
||||
if (n === 1) {
|
||||
return "Idatzi karaktere bat gutxiago";
|
||||
} else {
|
||||
return "Idatzi " + n + " karaktere gutxiago";
|
||||
}
|
||||
},
|
||||
formatSelectionTooBig: function (limit) {
|
||||
if (limit === 1 ) {
|
||||
return "Elementu bakarra hauta dezakezu";
|
||||
} else {
|
||||
return limit + " elementu hauta ditzakezu soilik";
|
||||
}
|
||||
},
|
||||
formatLoadMore: function (pageNumber) {
|
||||
return "Emaitza gehiago kargatzen…";
|
||||
},
|
||||
formatSearching: function () {
|
||||
return "Bilatzen…";
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
19
apps/files_external/3rdparty/select2/select2_locale_fa.js
vendored
Normal file
19
apps/files_external/3rdparty/select2/select2_locale_fa.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Select2 Persian translation.
|
||||
*
|
||||
* Author: Ali Choopan <choopan@arsh.co>
|
||||
* Author: Ebrahim Byagowi <ebrahim@gnu.org>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatMatches: function (matches) { return matches + " نتیجه موجود است، کلیدهای جهت بالا و پایین را برای گشتن استفاده کنید."; },
|
||||
formatNoMatches: function () { return "نتیجهای یافت نشد."; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "لطفاً " + n + " نویسه بیشتر وارد نمایید"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "لطفاً " + n + " نویسه را حذف کنید."; },
|
||||
formatSelectionTooBig: function (limit) { return "شما فقط میتوانید " + limit + " مورد را انتخاب کنید"; },
|
||||
formatLoadMore: function (pageNumber) { return "در حال بارگیری موارد بیشتر…"; },
|
||||
formatSearching: function () { return "در حال جستجو…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
28
apps/files_external/3rdparty/select2/select2_locale_fi.js
vendored
Normal file
28
apps/files_external/3rdparty/select2/select2_locale_fi.js
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Select2 Finnish translation
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () {
|
||||
return "Ei tuloksia";
|
||||
},
|
||||
formatInputTooShort: function (input, min) {
|
||||
var n = min - input.length;
|
||||
return "Ole hyvä ja anna " + n + " merkkiä lisää";
|
||||
},
|
||||
formatInputTooLong: function (input, max) {
|
||||
var n = input.length - max;
|
||||
return "Ole hyvä ja anna " + n + " merkkiä vähemmän";
|
||||
},
|
||||
formatSelectionTooBig: function (limit) {
|
||||
return "Voit valita ainoastaan " + limit + " kpl";
|
||||
},
|
||||
formatLoadMore: function (pageNumber) {
|
||||
return "Ladataan lisää tuloksia…";
|
||||
},
|
||||
formatSearching: function () {
|
||||
return "Etsitään…";
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
16
apps/files_external/3rdparty/select2/select2_locale_fr.js
vendored
Normal file
16
apps/files_external/3rdparty/select2/select2_locale_fr.js
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Select2 French translation
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatMatches: function (matches) { return matches + " résultats sont disponibles, utilisez les flèches haut et bas pour naviguer."; },
|
||||
formatNoMatches: function () { return "Aucun résultat trouvé"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Merci de saisir " + n + " caractère" + (n == 1 ? "" : "s") + " de plus"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Merci de supprimer " + n + " caractère" + (n == 1 ? "" : "s"); },
|
||||
formatSelectionTooBig: function (limit) { return "Vous pouvez seulement sélectionner " + limit + " élément" + (limit == 1 ? "" : "s"); },
|
||||
formatLoadMore: function (pageNumber) { return "Chargement de résultats supplémentaires…"; },
|
||||
formatSearching: function () { return "Recherche en cours…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
43
apps/files_external/3rdparty/select2/select2_locale_gl.js
vendored
Normal file
43
apps/files_external/3rdparty/select2/select2_locale_gl.js
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Select2 Galician translation
|
||||
*
|
||||
* Author: Leandro Regueiro <leandro.regueiro@gmail.com>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () {
|
||||
return "Non se atoparon resultados";
|
||||
},
|
||||
formatInputTooShort: function (input, min) {
|
||||
var n = min - input.length;
|
||||
if (n === 1) {
|
||||
return "Engada un carácter";
|
||||
} else {
|
||||
return "Engada " + n + " caracteres";
|
||||
}
|
||||
},
|
||||
formatInputTooLong: function (input, max) {
|
||||
var n = input.length - max;
|
||||
if (n === 1) {
|
||||
return "Elimine un carácter";
|
||||
} else {
|
||||
return "Elimine " + n + " caracteres";
|
||||
}
|
||||
},
|
||||
formatSelectionTooBig: function (limit) {
|
||||
if (limit === 1 ) {
|
||||
return "Só pode seleccionar un elemento";
|
||||
} else {
|
||||
return "Só pode seleccionar " + limit + " elementos";
|
||||
}
|
||||
},
|
||||
formatLoadMore: function (pageNumber) {
|
||||
return "Cargando máis resultados…";
|
||||
},
|
||||
formatSearching: function () {
|
||||
return "Buscando…";
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
17
apps/files_external/3rdparty/select2/select2_locale_he.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_he.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Hebrew translation.
|
||||
*
|
||||
* Author: Yakir Sitbon <http://www.yakirs.net/>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "לא נמצאו התאמות"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "נא להזין עוד " + n + " תווים נוספים"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "נא להזין פחות " + n + " תווים"; },
|
||||
formatSelectionTooBig: function (limit) { return "ניתן לבחור " + limit + " פריטים"; },
|
||||
formatLoadMore: function (pageNumber) { return "טוען תוצאות נוספות…"; },
|
||||
formatSearching: function () { return "מחפש…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
22
apps/files_external/3rdparty/select2/select2_locale_hr.js
vendored
Normal file
22
apps/files_external/3rdparty/select2/select2_locale_hr.js
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Select2 Croatian translation.
|
||||
*
|
||||
* @author Edi Modrić <edi.modric@gmail.com>
|
||||
* @author Uriy Efremochkin <efremochkin@uriy.me>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Nema rezultata"; },
|
||||
formatInputTooShort: function (input, min) { return "Unesite još" + character(min - input.length); },
|
||||
formatInputTooLong: function (input, max) { return "Unesite" + character(input.length - max) + " manje"; },
|
||||
formatSelectionTooBig: function (limit) { return "Maksimalan broj odabranih stavki je " + limit; },
|
||||
formatLoadMore: function (pageNumber) { return "Učitavanje rezultata…"; },
|
||||
formatSearching: function () { return "Pretraga…"; }
|
||||
});
|
||||
|
||||
function character (n) {
|
||||
return " " + n + " znak" + (n%10 < 5 && n%10 > 0 && (n%100 < 5 || n%100 > 19) ? n%10 > 1 ? "a" : "" : "ova");
|
||||
}
|
||||
})(jQuery);
|
||||
15
apps/files_external/3rdparty/select2/select2_locale_hu.js
vendored
Normal file
15
apps/files_external/3rdparty/select2/select2_locale_hu.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Select2 Hungarian translation
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Nincs találat."; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Túl rövid. Még " + n + " karakter hiányzik."; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Túl hosszú. " + n + " karakterrel több, mint kellene."; },
|
||||
formatSelectionTooBig: function (limit) { return "Csak " + limit + " elemet lehet kiválasztani."; },
|
||||
formatLoadMore: function (pageNumber) { return "Töltés…"; },
|
||||
formatSearching: function () { return "Keresés…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
17
apps/files_external/3rdparty/select2/select2_locale_id.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_id.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Indonesian translation.
|
||||
*
|
||||
* Author: Ibrahim Yusuf <ibrahim7usuf@gmail.com>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Tidak ada data yang sesuai"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Masukkan " + n + " huruf lagi" + (n == 1 ? "" : "s"); },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Hapus " + n + " huruf" + (n == 1 ? "" : "s"); },
|
||||
formatSelectionTooBig: function (limit) { return "Anda hanya dapat memilih " + limit + " pilihan" + (limit == 1 ? "" : "s"); },
|
||||
formatLoadMore: function (pageNumber) { return "Mengambil data…"; },
|
||||
formatSearching: function () { return "Mencari…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
15
apps/files_external/3rdparty/select2/select2_locale_is.js
vendored
Normal file
15
apps/files_external/3rdparty/select2/select2_locale_is.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Select2 Icelandic translation.
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Ekkert fannst"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Vinsamlegast skrifið " + n + " staf" + (n > 1 ? "i" : "") + " í viðbót"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Vinsamlegast styttið texta um " + n + " staf" + (n > 1 ? "i" : ""); },
|
||||
formatSelectionTooBig: function (limit) { return "Þú getur aðeins valið " + limit + " atriði"; },
|
||||
formatLoadMore: function (pageNumber) { return "Sæki fleiri niðurstöður…"; },
|
||||
formatSearching: function () { return "Leita…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
15
apps/files_external/3rdparty/select2/select2_locale_it.js
vendored
Normal file
15
apps/files_external/3rdparty/select2/select2_locale_it.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Select2 Italian translation
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Nessuna corrispondenza trovata"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Inserisci ancora " + n + " caratter" + (n == 1? "e" : "i"); },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Inserisci " + n + " caratter" + (n == 1? "e" : "i") + " in meno"; },
|
||||
formatSelectionTooBig: function (limit) { return "Puoi selezionare solo " + limit + " element" + (limit == 1 ? "o" : "i"); },
|
||||
formatLoadMore: function (pageNumber) { return "Caricamento in corso…"; },
|
||||
formatSearching: function () { return "Ricerca…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
15
apps/files_external/3rdparty/select2/select2_locale_ja.js
vendored
Normal file
15
apps/files_external/3rdparty/select2/select2_locale_ja.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Select2 Japanese translation.
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "該当なし"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "後" + n + "文字入れてください"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "検索文字列が" + n + "文字長すぎます"; },
|
||||
formatSelectionTooBig: function (limit) { return "最多で" + limit + "項目までしか選択できません"; },
|
||||
formatLoadMore: function (pageNumber) { return "読込中・・・"; },
|
||||
formatSearching: function () { return "検索中・・・"; }
|
||||
});
|
||||
})(jQuery);
|
||||
17
apps/files_external/3rdparty/select2/select2_locale_ka.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_ka.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Georgian (Kartuli) translation.
|
||||
*
|
||||
* Author: Dimitri Kurashvili dimakura@gmail.com
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "ვერ მოიძებნა"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "გთხოვთ შეიყვანოთ კიდევ " + n + " სიმბოლო"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "გთხოვთ წაშალოთ " + n + " სიმბოლო"; },
|
||||
formatSelectionTooBig: function (limit) { return "თქვენ შეგიძლიათ მხოლოდ " + limit + " ჩანაწერის მონიშვნა"; },
|
||||
formatLoadMore: function (pageNumber) { return "შედეგის ჩატვირთვა…"; },
|
||||
formatSearching: function () { return "ძებნა…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
17
apps/files_external/3rdparty/select2/select2_locale_ko.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_ko.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Korean translation.
|
||||
*
|
||||
* @author Swen Mun <longfinfunnel@gmail.com>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "결과 없음"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "너무 짧습니다. "+n+"글자 더 입력해주세요."; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "너무 깁니다. "+n+"글자 지워주세요."; },
|
||||
formatSelectionTooBig: function (limit) { return "최대 "+limit+"개까지만 선택하실 수 있습니다."; },
|
||||
formatLoadMore: function (pageNumber) { return "불러오는 중…"; },
|
||||
formatSearching: function () { return "검색 중…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
24
apps/files_external/3rdparty/select2/select2_locale_lt.js
vendored
Normal file
24
apps/files_external/3rdparty/select2/select2_locale_lt.js
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Select2 Lithuanian translation.
|
||||
*
|
||||
* @author CRONUS Karmalakas <cronus dot karmalakas at gmail dot com>
|
||||
* @author Uriy Efremochkin <efremochkin@uriy.me>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Atitikmenų nerasta"; },
|
||||
formatInputTooShort: function (input, min) { return "Įrašykite dar" + character(min - input.length); },
|
||||
formatInputTooLong: function (input, max) { return "Pašalinkite" + character(input.length - max); },
|
||||
formatSelectionTooBig: function (limit) {
|
||||
return "Jūs galite pasirinkti tik " + limit + " element" + ((limit%100 > 9 && limit%100 < 21) || limit%10 == 0 ? "ų" : limit%10 > 1 ? "us" : "ą");
|
||||
},
|
||||
formatLoadMore: function (pageNumber) { return "Kraunama daugiau rezultatų…"; },
|
||||
formatSearching: function () { return "Ieškoma…"; }
|
||||
});
|
||||
|
||||
function character (n) {
|
||||
return " " + n + " simbol" + ((n%100 > 9 && n%100 < 21) || n%10 == 0 ? "ių" : n%10 > 1 ? "ius" : "į");
|
||||
}
|
||||
})(jQuery);
|
||||
17
apps/files_external/3rdparty/select2/select2_locale_lv.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_lv.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Latvian translation.
|
||||
*
|
||||
* @author Uriy Efremochkin <efremochkin@uriy.me>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Sakritību nav"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Lūdzu ievadiet vēl " + n + " simbol" + (n == 11 ? "us" : n%10 == 1 ? "u" : "us"); },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Lūdzu ievadiet par " + n + " simbol" + (n == 11 ? "iem" : n%10 == 1 ? "u" : "iem") + " mazāk"; },
|
||||
formatSelectionTooBig: function (limit) { return "Jūs varat izvēlēties ne vairāk kā " + limit + " element" + (limit == 11 ? "us" : limit%10 == 1 ? "u" : "us"); },
|
||||
formatLoadMore: function (pageNumber) { return "Datu ielāde…"; },
|
||||
formatSearching: function () { return "Meklēšana…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
17
apps/files_external/3rdparty/select2/select2_locale_mk.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_mk.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Macedonian translation.
|
||||
*
|
||||
* Author: Marko Aleksic <psybaron@gmail.com>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Нема пронајдено совпаѓања"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Ве молиме внесете уште " + n + " карактер" + (n == 1 ? "" : "и"); },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Ве молиме внесете " + n + " помалку карактер" + (n == 1? "" : "и"); },
|
||||
formatSelectionTooBig: function (limit) { return "Можете да изберете само " + limit + " ставк" + (limit == 1 ? "а" : "и"); },
|
||||
formatLoadMore: function (pageNumber) { return "Вчитување резултати…"; },
|
||||
formatSearching: function () { return "Пребарување…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
17
apps/files_external/3rdparty/select2/select2_locale_ms.js
vendored
Normal file
17
apps/files_external/3rdparty/select2/select2_locale_ms.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Select2 Malay translation.
|
||||
*
|
||||
* Author: Kepoweran <kepoweran@gmail.com>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Tiada padanan yang ditemui"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Sila masukkan " + n + " aksara lagi"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Sila hapuskan " + n + " aksara"; },
|
||||
formatSelectionTooBig: function (limit) { return "Anda hanya boleh memilih " + limit + " pilihan"; },
|
||||
formatLoadMore: function (pageNumber) { return "Sedang memuatkan keputusan…"; },
|
||||
formatSearching: function () { return "Mencari…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
15
apps/files_external/3rdparty/select2/select2_locale_nl.js
vendored
Normal file
15
apps/files_external/3rdparty/select2/select2_locale_nl.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Select2 Dutch translation
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Geen resultaten gevonden"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Vul " + n + " karakter" + (n == 1? "" : "s") + " meer in"; },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Vul " + n + " karakter" + (n == 1? "" : "s") + " minder in"; },
|
||||
formatSelectionTooBig: function (limit) { return "Maximaal " + limit + " item" + (limit == 1 ? "" : "s") + " toegestaan"; },
|
||||
formatLoadMore: function (pageNumber) { return "Meer resultaten laden…"; },
|
||||
formatSearching: function () { return "Zoeken…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
18
apps/files_external/3rdparty/select2/select2_locale_no.js
vendored
Normal file
18
apps/files_external/3rdparty/select2/select2_locale_no.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Select2 Norwegian translation.
|
||||
*
|
||||
* Author: Torgeir Veimo <torgeir.veimo@gmail.com>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Ingen treff"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Vennligst skriv inn " + n + (n>1 ? " flere tegn" : " tegn til"); },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Vennligst fjern " + n + " tegn"; },
|
||||
formatSelectionTooBig: function (limit) { return "Du kan velge maks " + limit + " elementer"; },
|
||||
formatLoadMore: function (pageNumber) { return "Laster flere resultater…"; },
|
||||
formatSearching: function () { return "Søker…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
22
apps/files_external/3rdparty/select2/select2_locale_pl.js
vendored
Normal file
22
apps/files_external/3rdparty/select2/select2_locale_pl.js
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Select2 Polish translation.
|
||||
*
|
||||
* @author Jan Kondratowicz <jan@kondratowicz.pl>
|
||||
* @author Uriy Efremochkin <efremochkin@uriy.me>
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Brak wyników"; },
|
||||
formatInputTooShort: function (input, min) { return "Wpisz jeszcze" + character(min - input.length, "znak", "i"); },
|
||||
formatInputTooLong: function (input, max) { return "Wpisana fraza jest za długa o" + character(input.length - max, "znak", "i"); },
|
||||
formatSelectionTooBig: function (limit) { return "Możesz zaznaczyć najwyżej" + character(limit, "element", "y"); },
|
||||
formatLoadMore: function (pageNumber) { return "Ładowanie wyników…"; },
|
||||
formatSearching: function () { return "Szukanie…"; }
|
||||
});
|
||||
|
||||
function character (n, word, pluralSuffix) {
|
||||
return " " + n + " " + word + (n == 1 ? "" : n%10 < 5 && n%10 > 1 && (n%100 < 5 || n%100 > 20) ? pluralSuffix : "ów");
|
||||
}
|
||||
})(jQuery);
|
||||
15
apps/files_external/3rdparty/select2/select2_locale_pt-BR.js
vendored
Normal file
15
apps/files_external/3rdparty/select2/select2_locale_pt-BR.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Select2 Brazilian Portuguese translation
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Nenhum resultado encontrado"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Digite mais " + n + " caracter" + (n == 1? "" : "es"); },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Apague " + n + " caracter" + (n == 1? "" : "es"); },
|
||||
formatSelectionTooBig: function (limit) { return "Só é possível selecionar " + limit + " elemento" + (limit == 1 ? "" : "s"); },
|
||||
formatLoadMore: function (pageNumber) { return "Carregando mais resultados…"; },
|
||||
formatSearching: function () { return "Buscando…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
15
apps/files_external/3rdparty/select2/select2_locale_pt-PT.js
vendored
Normal file
15
apps/files_external/3rdparty/select2/select2_locale_pt-PT.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Select2 Portuguese (Portugal) translation
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.extend($.fn.select2.defaults, {
|
||||
formatNoMatches: function () { return "Nenhum resultado encontrado"; },
|
||||
formatInputTooShort: function (input, min) { var n = min - input.length; return "Introduza " + n + " car" + (n == 1 ? "ácter" : "acteres"); },
|
||||
formatInputTooLong: function (input, max) { var n = input.length - max; return "Apague " + n + " car" + (n == 1 ? "ácter" : "acteres"); },
|
||||
formatSelectionTooBig: function (limit) { return "Só é possível selecionar " + limit + " elemento" + (limit == 1 ? "" : "s"); },
|
||||
formatLoadMore: function (pageNumber) { return "A carregar mais resultados…"; },
|
||||
formatSearching: function () { return "A pesquisar…"; }
|
||||
});
|
||||
})(jQuery);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user