python-plugins 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_plugins-0.2.1 → python_plugins-0.2.3}/CHANGES.rst +17 -3
- {python_plugins-0.2.1 → python_plugins-0.2.3}/PKG-INFO +3 -4
- {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/usage.rst +1 -1
- {python_plugins-0.2.1 → python_plugins-0.2.3}/pyproject.toml +0 -1
- python_plugins-0.2.3/requirements/develop.in +1 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/requirements/test.in +0 -1
- python_plugins-0.2.3/src/python_plugins/__about__.py +1 -0
- python_plugins-0.2.3/src/python_plugins/crypto/zipmix.py +264 -0
- python_plugins-0.2.3/src/python_plugins/email/__init__.py +1 -0
- python_plugins-0.2.3/src/python_plugins/email/smtp.py +51 -0
- python_plugins-0.2.3/src/python_plugins/utils/update-icons.py +49 -0
- python_plugins-0.2.3/tests/test_email.py +83 -0
- python_plugins-0.2.3/tests/test_zipmix.py +142 -0
- python_plugins-0.2.3/tests/tmp/a.png +0 -0
- python_plugins-0.2.3/tests/tmp/a.txt +1 -0
- python_plugins-0.2.3/tests/tmp/test.txt +2 -0
- python_plugins-0.2.3/tests/tmp/testdir/test2.txt +2 -0
- python_plugins-0.2.1/src/python_plugins/__about__.py +0 -1
- python_plugins-0.2.1/src/python_plugins/email/smtp.py +0 -34
- python_plugins-0.2.1/src/python_plugins/forms/__init__.py +0 -0
- python_plugins-0.2.1/src/python_plugins/forms/fields/__init__.py +0 -2
- python_plugins-0.2.1/src/python_plugins/forms/fields/datetime.py +0 -100
- python_plugins-0.2.1/src/python_plugins/forms/fields/json.py +0 -19
- python_plugins-0.2.1/src/python_plugins/forms/fields/select.py +0 -123
- python_plugins-0.2.1/src/python_plugins/forms/fields/switch.py +0 -10
- python_plugins-0.2.1/src/python_plugins/forms/fields/taglist.py +0 -9
- python_plugins-0.2.1/src/python_plugins/forms/mixins/__init__.py +0 -0
- python_plugins-0.2.1/src/python_plugins/forms/mixins/user.py +0 -29
- python_plugins-0.2.1/src/python_plugins/forms/widgets/__init__.py +0 -0
- python_plugins-0.2.1/src/python_plugins/forms/widgets/datetime.py +0 -30
- python_plugins-0.2.1/src/python_plugins/forms/widgets/file.py +0 -4
- python_plugins-0.2.1/src/python_plugins/forms/widgets/select.py +0 -26
- python_plugins-0.2.1/src/python_plugins/models/__init__.py +0 -0
- python_plugins-0.2.1/tests/test_email.py +0 -25
- python_plugins-0.2.1/tests/test_forms.py +0 -65
- {python_plugins-0.2.1 → python_plugins-0.2.3}/.github/workflows/release.yml +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/.gitignore +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/.readthedocs.yaml +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/LICENSE.rst +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/README.rst +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/Makefile +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/changes.rst +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/conf.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/index.rst +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/make.bat +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/docs/requirements.txt +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/requirements/build.in +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/convert/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/convert/datetime_str.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/convert/pretty.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/convert/xml.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/crypto/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/crypto/fernet.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/crypto/file_to_file.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/crypto/str_to_list.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/dumps/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/dumps/postgresql_dump.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/examples/higher_order_functions.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/hashes/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/hashes/hash.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/jwt/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/jwt/jwt.py +0 -0
- {python_plugins-0.2.1/src/python_plugins/email → python_plugins-0.2.3/src/python_plugins/models}/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/data_mixin.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/primary_key_mixin.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/timestamp_mixin.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/token_minxin.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/mixins/user_minxin.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/models/update.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/process/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/process/python_venv_process.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/process/sub_process.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/random/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/random/random_str.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/utils/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/utils/walk_remove_dir.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/biz_data_crypt.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/error_code.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/format_response.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/wechat.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/wechat_crypt.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/src/python_plugins/weixin/weixin_api.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/__init__.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/conftest.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_crypt_file.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_crypto.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_jwt.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_process.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_random.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_sqlalchemy.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_utils.py +0 -0
- {python_plugins-0.2.1 → python_plugins-0.2.3}/tests/test_weixin.py +0 -0
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
v0.2.3
|
|
2
|
+
------
|
|
3
|
+
|
|
4
|
+
Released 2025-11-02
|
|
5
|
+
|
|
6
|
+
- zip7
|
|
7
|
+
|
|
8
|
+
v0.2.2
|
|
9
|
+
------
|
|
10
|
+
|
|
11
|
+
Released 2024-12-27
|
|
12
|
+
|
|
13
|
+
- update-icons
|
|
14
|
+
|
|
1
15
|
v0.2.1
|
|
2
16
|
------
|
|
3
17
|
|
|
@@ -10,7 +24,7 @@ v0.2.0
|
|
|
10
24
|
|
|
11
25
|
Released 2024-11-20
|
|
12
26
|
|
|
13
|
-
- add
|
|
27
|
+
- add random
|
|
14
28
|
|
|
15
29
|
|
|
16
30
|
v0.1.9
|
|
@@ -18,7 +32,7 @@ v0.1.9
|
|
|
18
32
|
|
|
19
33
|
Released 2024-11-19
|
|
20
34
|
|
|
21
|
-
- add
|
|
35
|
+
- add convert
|
|
22
36
|
|
|
23
37
|
|
|
24
38
|
v0.1.8
|
|
@@ -62,7 +76,7 @@ v0.1.3
|
|
|
62
76
|
|
|
63
77
|
Released 2024-09-19
|
|
64
78
|
|
|
65
|
-
- support
|
|
79
|
+
- support hash_file,fernet
|
|
66
80
|
|
|
67
81
|
v0.1.2
|
|
68
82
|
------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-plugins
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: A collection of Python functions and classes.
|
|
5
5
|
Project-URL: Documentation, https://python-plugins.readthedocs.io
|
|
6
6
|
Project-URL: Source, https://github.com/ojso/python-plugins
|
|
@@ -27,6 +27,7 @@ License: MIT License
|
|
|
27
27
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
28
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
29
|
SOFTWARE.
|
|
30
|
+
License-File: LICENSE.rst
|
|
30
31
|
Keywords: plugin,utils
|
|
31
32
|
Classifier: Development Status :: 3 - Alpha
|
|
32
33
|
Classifier: Intended Audience :: Developers
|
|
@@ -49,8 +50,6 @@ Provides-Extra: requests
|
|
|
49
50
|
Requires-Dist: requests; extra == 'requests'
|
|
50
51
|
Provides-Extra: sqlalchemy
|
|
51
52
|
Requires-Dist: sqlalchemy; extra == 'sqlalchemy'
|
|
52
|
-
Provides-Extra: wtforms
|
|
53
|
-
Requires-Dist: wtforms; extra == 'wtforms'
|
|
54
53
|
Description-Content-Type: text/x-rst
|
|
55
54
|
|
|
56
55
|
python-plugins
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lxml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.3"
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import random
|
|
3
|
+
import hashlib
|
|
4
|
+
import tempfile
|
|
5
|
+
import filecmp
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ZipMix:
|
|
11
|
+
def __init__(self, zipcmd="7z"):
|
|
12
|
+
self.zipcmd = zipcmd
|
|
13
|
+
self.z7_available = self.check_7z_available()
|
|
14
|
+
|
|
15
|
+
def check_7z_available(self) -> bool:
|
|
16
|
+
try:
|
|
17
|
+
subprocess.run([self.zipcmd], capture_output=True, check=True)
|
|
18
|
+
return True
|
|
19
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
def create_mix_flag(self, pos: int, nlen: int) -> bytes:
|
|
23
|
+
"""m{}i{}x{}{nlen}{}"""
|
|
24
|
+
start_bytes = pos.to_bytes(4, byteorder="big")
|
|
25
|
+
return b"m%si%sx%s%s%s" % (
|
|
26
|
+
start_bytes[0:1],
|
|
27
|
+
start_bytes[1:2],
|
|
28
|
+
start_bytes[2:3],
|
|
29
|
+
nlen.to_bytes(1, byteorder="big"),
|
|
30
|
+
start_bytes[3:4],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def int_to_bytes(self, pos: int):
|
|
34
|
+
return pos.to_bytes(4, byteorder="big")
|
|
35
|
+
|
|
36
|
+
def int_from_bytes(self, intbs):
|
|
37
|
+
return int.from_bytes(intbs, byteorder="big")
|
|
38
|
+
|
|
39
|
+
def extract_from_mix_flag(self, mix_flag):
|
|
40
|
+
fix_flag = mix_flag[0:1] + mix_flag[2:3] + mix_flag[4:5]
|
|
41
|
+
if fix_flag != b"mix":
|
|
42
|
+
raise ValueError("not match mix.")
|
|
43
|
+
nlen = self.int_from_bytes(mix_flag[6:7])
|
|
44
|
+
pos = self.int_from_bytes(
|
|
45
|
+
mix_flag[1:2] + mix_flag[3:4] + mix_flag[5:6] + mix_flag[7:8]
|
|
46
|
+
)
|
|
47
|
+
return (pos, nlen)
|
|
48
|
+
|
|
49
|
+
def insert_string_to_binary(self, data, insert_bytes):
|
|
50
|
+
n_data = len(data)
|
|
51
|
+
n_ibytes = len(insert_bytes)
|
|
52
|
+
pos_list = sorted(random.sample(range(min(n_data, 0xFFFFFFFF)), n_ibytes))
|
|
53
|
+
|
|
54
|
+
parts = []
|
|
55
|
+
pre_pos = 0
|
|
56
|
+
|
|
57
|
+
for i, curr_pos in enumerate(pos_list):
|
|
58
|
+
if i == 0:
|
|
59
|
+
end_flag = self.create_mix_flag(curr_pos, n_ibytes)
|
|
60
|
+
else:
|
|
61
|
+
parts.append(self.int_to_bytes(curr_pos))
|
|
62
|
+
|
|
63
|
+
parts.append(data[pre_pos:curr_pos])
|
|
64
|
+
parts.append(insert_bytes[i : i + 1])
|
|
65
|
+
pre_pos = curr_pos
|
|
66
|
+
|
|
67
|
+
parts.append(data[pre_pos:])
|
|
68
|
+
parts.append(end_flag)
|
|
69
|
+
return b"".join(parts)
|
|
70
|
+
|
|
71
|
+
def extract_string_from_binary(self, data):
|
|
72
|
+
start_pos, n_insert_bytes = self.extract_from_mix_flag(data[-8:])
|
|
73
|
+
parts = []
|
|
74
|
+
insert_parts = []
|
|
75
|
+
pos_list = [start_pos]
|
|
76
|
+
data_pre_pos = 0
|
|
77
|
+
curr_pos = start_pos
|
|
78
|
+
|
|
79
|
+
for k in range(n_insert_bytes):
|
|
80
|
+
data_curr_pos = curr_pos + 5 * k
|
|
81
|
+
parts.append(data[data_pre_pos:data_curr_pos])
|
|
82
|
+
insert_parts.append(data[data_curr_pos : data_curr_pos + 1])
|
|
83
|
+
if k < n_insert_bytes - 1:
|
|
84
|
+
curr_pos = self.int_from_bytes(
|
|
85
|
+
data[data_curr_pos + 1 : data_curr_pos + 5]
|
|
86
|
+
)
|
|
87
|
+
pos_list.append(curr_pos)
|
|
88
|
+
data_pre_pos = data_curr_pos + 5
|
|
89
|
+
else:
|
|
90
|
+
curr_pos = None
|
|
91
|
+
data_pre_pos = data_curr_pos + 1
|
|
92
|
+
|
|
93
|
+
parts.append(data[data_pre_pos:-8])
|
|
94
|
+
return b"".join(insert_parts), b"".join(parts)
|
|
95
|
+
|
|
96
|
+
def _generate_password(self) -> str:
|
|
97
|
+
return hashlib.sha256(os.urandom(1000)).hexdigest()[:32]
|
|
98
|
+
|
|
99
|
+
def zip7(self, file_or_dir, archive_path=None, pwd=None, force_pwd=False,silent = True):
|
|
100
|
+
path_obj = Path(file_or_dir)
|
|
101
|
+
if not path_obj.exists():
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
if archive_path is None:
|
|
105
|
+
archive_path = file_or_dir + ".7z"
|
|
106
|
+
|
|
107
|
+
if pwd is None:
|
|
108
|
+
if force_pwd:
|
|
109
|
+
pwd = self._generate_password()
|
|
110
|
+
else:
|
|
111
|
+
pwd = pwd[:32]
|
|
112
|
+
|
|
113
|
+
cmd = [self.zipcmd, "a"]
|
|
114
|
+
if silent:
|
|
115
|
+
cmd.append("-bso0")
|
|
116
|
+
if pwd:
|
|
117
|
+
cmd.extend([f"-p{pwd}", "-mhe=on"])
|
|
118
|
+
cmd.extend([archive_path, file_or_dir])
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
subprocess.run(cmd, check=True)
|
|
122
|
+
if pwd is not None:
|
|
123
|
+
return ("ok", archive_path, pwd)
|
|
124
|
+
else:
|
|
125
|
+
return ("ok", archive_path)
|
|
126
|
+
except subprocess.CalledProcessError as e:
|
|
127
|
+
return ("fail", f"{e}")
|
|
128
|
+
|
|
129
|
+
def unzip7_list_archive(self, archive_path, pwd=None,silent = True):
|
|
130
|
+
cmd = [self.zipcmd, "l"]
|
|
131
|
+
if silent:
|
|
132
|
+
cmd.append("-bso0")
|
|
133
|
+
if pwd:
|
|
134
|
+
cmd.append(f"-p{pwd}")
|
|
135
|
+
cmd.append(archive_path)
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
subprocess.run(cmd, check=True)
|
|
139
|
+
return ("ok", archive_path)
|
|
140
|
+
except subprocess.CalledProcessError as e:
|
|
141
|
+
return ("fail", f"{e}")
|
|
142
|
+
|
|
143
|
+
def unzip7(self, archive_path, output_path=None, pwd=None, overwrite: bool = True,silent = True):
|
|
144
|
+
archive_obj = Path(archive_path)
|
|
145
|
+
|
|
146
|
+
if output_path is None:
|
|
147
|
+
output_dir = archive_obj.parent
|
|
148
|
+
else:
|
|
149
|
+
output_dir = Path(output_path)
|
|
150
|
+
|
|
151
|
+
if not output_dir.exists():
|
|
152
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
153
|
+
|
|
154
|
+
if not output_dir.is_dir():
|
|
155
|
+
return ("fail", f"{output_dir} is not dir")
|
|
156
|
+
|
|
157
|
+
cmd = [self.zipcmd, "x", "-y"]
|
|
158
|
+
if silent:
|
|
159
|
+
cmd.append("-bso0")
|
|
160
|
+
if not overwrite:
|
|
161
|
+
cmd.append("-aos")
|
|
162
|
+
if pwd:
|
|
163
|
+
cmd.append(f"-p{pwd}")
|
|
164
|
+
cmd.extend([archive_path, f"-o{output_dir}"])
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
subprocess.run(cmd, check=True)
|
|
168
|
+
return ("ok", f"{output_dir}")
|
|
169
|
+
except subprocess.CalledProcessError as e:
|
|
170
|
+
return ("fail", f"{e}")
|
|
171
|
+
|
|
172
|
+
def zip7mix(self, file_or_dir, archive_path=None,silent = True):
|
|
173
|
+
path_obj = Path(file_or_dir)
|
|
174
|
+
if not path_obj.exists():
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
if archive_path is None:
|
|
178
|
+
archive_path = file_or_dir + ".7z"
|
|
179
|
+
|
|
180
|
+
tmp_archive_path = archive_path+".tmp"
|
|
181
|
+
|
|
182
|
+
tmp_obj = Path(tmp_archive_path)
|
|
183
|
+
if tmp_obj.exists():
|
|
184
|
+
tmp_obj.unlink()
|
|
185
|
+
|
|
186
|
+
pwd = self._generate_password()
|
|
187
|
+
|
|
188
|
+
cmd = [self.zipcmd, "a"]
|
|
189
|
+
if silent:
|
|
190
|
+
cmd.append("-bso0")
|
|
191
|
+
cmd.extend([f"-p{pwd}", "-mhe=on"])
|
|
192
|
+
cmd.extend([tmp_archive_path, file_or_dir])
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
subprocess.run(cmd, check=True)
|
|
196
|
+
except subprocess.CalledProcessError as e:
|
|
197
|
+
return ("fail", f"{e}")
|
|
198
|
+
|
|
199
|
+
with open(tmp_archive_path,"rb") as f1:
|
|
200
|
+
data = f1.read()
|
|
201
|
+
|
|
202
|
+
# remove tmp file
|
|
203
|
+
tmp_obj.unlink()
|
|
204
|
+
|
|
205
|
+
new_data = self.insert_string_to_binary(data,pwd.encode('utf-8'))
|
|
206
|
+
|
|
207
|
+
# append fake 7z tail
|
|
208
|
+
new_data += data[-100:]
|
|
209
|
+
|
|
210
|
+
with open(archive_path,"wb") as f2:
|
|
211
|
+
f2.write(new_data)
|
|
212
|
+
|
|
213
|
+
return ("ok", archive_path)
|
|
214
|
+
|
|
215
|
+
def unzip7mix(self, archive_path, output_path=None, overwrite: bool = True,silent = True):
|
|
216
|
+
archive_obj = Path(archive_path)
|
|
217
|
+
|
|
218
|
+
tmp_archive_path = archive_path+".tmp"
|
|
219
|
+
|
|
220
|
+
tmp_obj = Path(tmp_archive_path)
|
|
221
|
+
if tmp_obj.exists():
|
|
222
|
+
tmp_obj.unlink()
|
|
223
|
+
|
|
224
|
+
if output_path is None:
|
|
225
|
+
output_dir = archive_obj.parent
|
|
226
|
+
else:
|
|
227
|
+
output_dir = Path(output_path)
|
|
228
|
+
|
|
229
|
+
if not output_dir.exists():
|
|
230
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
231
|
+
|
|
232
|
+
if not output_dir.is_dir():
|
|
233
|
+
return ("fail", f"{output_dir} is not dir")
|
|
234
|
+
|
|
235
|
+
with open(archive_path,"rb") as f1:
|
|
236
|
+
data = f1.read()
|
|
237
|
+
# remove fake 7z tail
|
|
238
|
+
data = data[:-100]
|
|
239
|
+
|
|
240
|
+
pwdbts,new_data = self.extract_string_from_binary(data)
|
|
241
|
+
|
|
242
|
+
pwd = pwdbts.decode("utf-8")
|
|
243
|
+
|
|
244
|
+
with open(tmp_archive_path,"wb") as f2:
|
|
245
|
+
f2.write(new_data)
|
|
246
|
+
|
|
247
|
+
cmd = [self.zipcmd, "x", "-y"]
|
|
248
|
+
if silent:
|
|
249
|
+
cmd.append("-bso0")
|
|
250
|
+
if not overwrite:
|
|
251
|
+
cmd.append("-aos")
|
|
252
|
+
cmd.append(f"-p{pwd}")
|
|
253
|
+
cmd.extend([tmp_archive_path, f"-o{output_dir}"])
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
subprocess.run(cmd, check=True)
|
|
257
|
+
tmp_obj.unlink()
|
|
258
|
+
return ("ok", f"{output_dir}")
|
|
259
|
+
except subprocess.CalledProcessError as e:
|
|
260
|
+
return ("fail", f"{e}")
|
|
261
|
+
|
|
262
|
+
def filecmp(self,f1,f2):
|
|
263
|
+
result = filecmp.cmp(f1, f2, shallow=False)
|
|
264
|
+
return result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .smtp import SmtpSSL
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import smtplib
|
|
2
|
+
from email.message import EmailMessage
|
|
3
|
+
import mimetypes
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SmtpSSL:
|
|
7
|
+
def __init__(self, host, port, user, password):
|
|
8
|
+
self.host = host
|
|
9
|
+
self.port = port
|
|
10
|
+
self.user = user
|
|
11
|
+
self.password = password
|
|
12
|
+
|
|
13
|
+
def send_emsg(self, data):
|
|
14
|
+
emsg = EmailMessage()
|
|
15
|
+
emsg["Subject"] = data["subject"]
|
|
16
|
+
emsg["From"] = data.get("From", self.user)
|
|
17
|
+
emsg["To"] = data["to"]
|
|
18
|
+
|
|
19
|
+
if "cc" in data:
|
|
20
|
+
emsg["Cc"] = data["cc"]
|
|
21
|
+
|
|
22
|
+
if "bcc" in data:
|
|
23
|
+
emsg["Bcc"] = data["bcc"]
|
|
24
|
+
|
|
25
|
+
# Check if HTML content is provided
|
|
26
|
+
if "html_content" in data:
|
|
27
|
+
emsg.add_alternative(data["html_content"], subtype="html")
|
|
28
|
+
else:
|
|
29
|
+
emsg.set_content(data["content"])
|
|
30
|
+
|
|
31
|
+
# Add attachments if provided
|
|
32
|
+
if "attachments" in data:
|
|
33
|
+
for file_path in data["attachments"]:
|
|
34
|
+
ctype, encoding = mimetypes.guess_type(file_path)
|
|
35
|
+
if ctype is None or encoding is not None:
|
|
36
|
+
ctype = "application/octet-stream"
|
|
37
|
+
maintype, subtype = ctype.split("/", 1)
|
|
38
|
+
|
|
39
|
+
with open(file_path, "rb") as f:
|
|
40
|
+
emsg.add_attachment(
|
|
41
|
+
f.read(),
|
|
42
|
+
maintype=maintype,
|
|
43
|
+
subtype=subtype,
|
|
44
|
+
filename=file_path.split("\\")[-1],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
with smtplib.SMTP_SSL(self.host, self.port) as smtp:
|
|
48
|
+
# smtp.set_debuglevel(1)
|
|
49
|
+
smtp.login(self.user, self.password)
|
|
50
|
+
senderrs = smtp.send_message(emsg)
|
|
51
|
+
return senderrs
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Generate example page with overview of all icons."""
|
|
2
|
+
|
|
3
|
+
from lxml import etree
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse(filename):
|
|
7
|
+
"""Parse SVG file containing icons."""
|
|
8
|
+
symbols = set()
|
|
9
|
+
data = etree.parse(filename)
|
|
10
|
+
svg = data.getroot()
|
|
11
|
+
for symbol in svg:
|
|
12
|
+
symbols.add(symbol.attrib["id"])
|
|
13
|
+
return sorted(symbols)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def generate(version):
|
|
17
|
+
"""Write the HTML template file."""
|
|
18
|
+
head = f"""{{% extends "base.html" %}}
|
|
19
|
+
{{% from 'macro/icon.html' import render_icon %}}
|
|
20
|
+
<!-- DO NOT EDIT! Use update-icons.py for updating this file. -->
|
|
21
|
+
{{% block content %}}
|
|
22
|
+
<h2>Icons</h2>
|
|
23
|
+
<p>These are all the icons which are currently supported by Bootstrap-Flask.</p>
|
|
24
|
+
<ul class="row row-cols-3 row-cols-sm-4 row-cols-lg-6 row-cols-xl-8 list-unstyled list">
|
|
25
|
+
"""
|
|
26
|
+
names = parse(f"icons/bootstrap-icons.svg")
|
|
27
|
+
with open(f"bootstrap{version}/templates/icons.html", "w") as file:
|
|
28
|
+
file.write(head)
|
|
29
|
+
number = 0
|
|
30
|
+
for name in sorted(names):
|
|
31
|
+
item = f"""<li class="col mb-4">
|
|
32
|
+
<a class="d-block text-dark text-body-emphasis text-decoration-none" href="https://icons.getbootstrap.com/icons/{name}/">
|
|
33
|
+
<div class="px-3 py-4 mb-2 bg-light text-center rounded">
|
|
34
|
+
{{{{ render_icon('{name}', 32) }}}}
|
|
35
|
+
</div>
|
|
36
|
+
</a>
|
|
37
|
+
<div class="name text-body-secondary text-decoration-none text-center pt-1">{name}</div>
|
|
38
|
+
</li>
|
|
39
|
+
"""
|
|
40
|
+
file.write(item)
|
|
41
|
+
number += 1
|
|
42
|
+
file.write("</ul>\n")
|
|
43
|
+
file.write(f"<p>This is a total of {number} icons.</p>\n")
|
|
44
|
+
file.write("{% endblock %}\n")
|
|
45
|
+
print(f"For Bootstrap{version}, a total of {number} icons are supported.")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
for value in (4, 5):
|
|
49
|
+
generate(value)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import os.path as op
|
|
2
|
+
import pytest
|
|
3
|
+
from python_plugins.email import SmtpSSL
|
|
4
|
+
|
|
5
|
+
@pytest.mark.skip(reason="test ok")
|
|
6
|
+
def test_send_content():
|
|
7
|
+
host = "smtp.qiye.aliyun.com"
|
|
8
|
+
port = "465"
|
|
9
|
+
user = "test@ojso.com"
|
|
10
|
+
password = "gTk94GEeNvujr4zm"
|
|
11
|
+
|
|
12
|
+
to = "test@ojso.com"
|
|
13
|
+
data = {
|
|
14
|
+
"to": to,
|
|
15
|
+
"subject": "test send content",
|
|
16
|
+
"content": "This is a plain text email.",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
s = SmtpSSL(host, port, user, password)
|
|
20
|
+
r = s.send_emsg(data)
|
|
21
|
+
|
|
22
|
+
assert not r
|
|
23
|
+
|
|
24
|
+
@pytest.mark.skip(reason="test ok")
|
|
25
|
+
def test_send_html():
|
|
26
|
+
host = "smtp.qiye.aliyun.com"
|
|
27
|
+
port = "465"
|
|
28
|
+
user = "test@ojso.com"
|
|
29
|
+
password = "gTk94GEeNvujr4zm"
|
|
30
|
+
|
|
31
|
+
to = "test@ojso.com"
|
|
32
|
+
html_content = """
|
|
33
|
+
<html>
|
|
34
|
+
<body>
|
|
35
|
+
<h1>This is a HTML email.</h1>
|
|
36
|
+
<p>You can use <strong>HTML</strong> format!</p>
|
|
37
|
+
<p><a href="https://www.example.com">Click here to access the sample website</a></p>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
data = {
|
|
43
|
+
"to": to,
|
|
44
|
+
"subject": "test send html",
|
|
45
|
+
"html_content": html_content,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
s = SmtpSSL(host, port, user, password)
|
|
49
|
+
r = s.send_emsg(data)
|
|
50
|
+
|
|
51
|
+
assert not r
|
|
52
|
+
|
|
53
|
+
@pytest.mark.skip(reason="test ok")
|
|
54
|
+
def test_send_attach():
|
|
55
|
+
host = "smtp.qiye.aliyun.com"
|
|
56
|
+
port = "465"
|
|
57
|
+
user = "test@ojso.com"
|
|
58
|
+
password = "gTk94GEeNvujr4zm"
|
|
59
|
+
|
|
60
|
+
to = "test@ojso.com"
|
|
61
|
+
html_content = """
|
|
62
|
+
<html>
|
|
63
|
+
<body>
|
|
64
|
+
<h1>This a HTML email with attach</h1>
|
|
65
|
+
<p>have a look at attach</p>
|
|
66
|
+
</body>
|
|
67
|
+
</html>
|
|
68
|
+
"""
|
|
69
|
+
parent_dir = op.realpath(op.dirname(__file__))
|
|
70
|
+
attach_path_1 = op.join(parent_dir, "tmp","a.txt")
|
|
71
|
+
attach_path_2 = op.join(parent_dir, "tmp","a.png")
|
|
72
|
+
|
|
73
|
+
data = {
|
|
74
|
+
"to": to,
|
|
75
|
+
"subject": "test send attach",
|
|
76
|
+
"html_content": html_content,
|
|
77
|
+
"attachments": [attach_path_1, attach_path_2],
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
s = SmtpSSL(host, port, user, password)
|
|
81
|
+
r = s.send_emsg(data)
|
|
82
|
+
|
|
83
|
+
assert not r
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import os
|
|
3
|
+
import os.path as op
|
|
4
|
+
import shutil
|
|
5
|
+
from python_plugins.crypto.zipmix import ZipMix
|
|
6
|
+
|
|
7
|
+
def test_z7_available():
|
|
8
|
+
zm = ZipMix()
|
|
9
|
+
assert zm.z7_available is True
|
|
10
|
+
|
|
11
|
+
def test_int_bytes_roundtrip():
|
|
12
|
+
zm = ZipMix()
|
|
13
|
+
for v in (0, 1, 255, 256, 65535, 2**24 - 1):
|
|
14
|
+
b = zm.int_to_bytes(v)
|
|
15
|
+
assert isinstance(b, bytes)
|
|
16
|
+
assert zm.int_from_bytes(b) == v
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_create_and_extract_mix_flag():
|
|
20
|
+
zm = ZipMix()
|
|
21
|
+
pos = 0x01020304
|
|
22
|
+
nlen = 7
|
|
23
|
+
flag = zm.create_mix_flag(pos, nlen)
|
|
24
|
+
assert isinstance(flag, bytes)
|
|
25
|
+
assert len(flag) == 8
|
|
26
|
+
extracted = zm.extract_from_mix_flag(flag)
|
|
27
|
+
assert extracted == (pos, nlen)
|
|
28
|
+
with pytest.raises(ValueError):
|
|
29
|
+
zm.extract_from_mix_flag(b"0" * 8)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_insert_string_to_binary():
|
|
33
|
+
zm = ZipMix()
|
|
34
|
+
data = os.urandom(500)
|
|
35
|
+
insert = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
36
|
+
new_1 = zm.insert_string_to_binary(data, insert)
|
|
37
|
+
assert len(new_1) - len(data) == len(insert) * 5 + 4
|
|
38
|
+
old_insert, old = zm.extract_string_from_binary(new_1)
|
|
39
|
+
assert old_insert == insert
|
|
40
|
+
assert data == old
|
|
41
|
+
new_2 = zm.insert_string_to_binary(data, insert)
|
|
42
|
+
assert len(new_2) == len(new_1)
|
|
43
|
+
assert new_2 != new_1
|
|
44
|
+
old_insert, old = zm.extract_string_from_binary(new_2)
|
|
45
|
+
assert old_insert == insert
|
|
46
|
+
assert data == old
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_zip7():
|
|
50
|
+
parent_dir = op.realpath(op.dirname(__file__))
|
|
51
|
+
f_test = op.join(parent_dir, "tmp","test.txt")
|
|
52
|
+
f_test_7z = op.join(parent_dir, "tmp","test.txt.7z")
|
|
53
|
+
if os.path.exists(f_test_7z):
|
|
54
|
+
os.unlink(f_test_7z)
|
|
55
|
+
dir_test_out = op.join(parent_dir, "tmp","out")
|
|
56
|
+
f_test_out = op.join(parent_dir, "tmp","out","test.txt")
|
|
57
|
+
|
|
58
|
+
zm = ZipMix()
|
|
59
|
+
r_zip=zm.zip7(f_test)
|
|
60
|
+
assert len(r_zip)==2
|
|
61
|
+
assert r_zip[0] == "ok"
|
|
62
|
+
f_zip = r_zip[1]
|
|
63
|
+
# r_list = zm.unzip7_list_archive(f_zip)
|
|
64
|
+
# assert r_list[0] == "ok"
|
|
65
|
+
r_unzip = zm.unzip7(f_zip,dir_test_out)
|
|
66
|
+
assert r_unzip[0] == "ok"
|
|
67
|
+
r_cmp = zm.filecmp(f_test,f_test_out)
|
|
68
|
+
assert r_cmp is True
|
|
69
|
+
if os.path.exists(f_zip):
|
|
70
|
+
os.unlink(f_zip)
|
|
71
|
+
if os.path.exists(f_test_out):
|
|
72
|
+
os.unlink(f_test_out)
|
|
73
|
+
|
|
74
|
+
def test_zip7_dir():
|
|
75
|
+
parent_dir = op.realpath(op.dirname(__file__))
|
|
76
|
+
f_test = op.join(parent_dir, "tmp","testdir")
|
|
77
|
+
f_test_7z = op.join(parent_dir, "tmp","testdir.7z")
|
|
78
|
+
if os.path.exists(f_test_7z):
|
|
79
|
+
os.unlink(f_test_7z)
|
|
80
|
+
dir_test_out = op.join(parent_dir, "tmp","out","testdir")
|
|
81
|
+
zm = ZipMix()
|
|
82
|
+
r_zip=zm.zip7(f_test)
|
|
83
|
+
assert len(r_zip)==2
|
|
84
|
+
assert r_zip[0] == "ok"
|
|
85
|
+
f_zip = r_zip[1]
|
|
86
|
+
# r_list = zm.unzip7_list_archive(f_zip)
|
|
87
|
+
# assert r_list[0] == "ok"
|
|
88
|
+
r_unzip = zm.unzip7(f_zip,dir_test_out)
|
|
89
|
+
assert r_unzip[0] == "ok"
|
|
90
|
+
# r_cmp = zm.filecmp(f_test,f_test_out)
|
|
91
|
+
# assert r_cmp is True
|
|
92
|
+
if os.path.exists(f_zip):
|
|
93
|
+
os.unlink(f_zip)
|
|
94
|
+
if os.path.exists(dir_test_out):
|
|
95
|
+
shutil.rmtree(dir_test_out)
|
|
96
|
+
|
|
97
|
+
def test_zip7_force_password():
|
|
98
|
+
parent_dir = op.realpath(op.dirname(__file__))
|
|
99
|
+
f_test = op.join(parent_dir, "tmp","test.txt")
|
|
100
|
+
f_test_7z = op.join(parent_dir, "tmp","test.txt.7z")
|
|
101
|
+
if os.path.exists(f_test_7z):
|
|
102
|
+
os.unlink(f_test_7z)
|
|
103
|
+
dir_test_out = op.join(parent_dir, "tmp","out")
|
|
104
|
+
f_test_out = op.join(parent_dir, "tmp","out","test.txt")
|
|
105
|
+
zm = ZipMix()
|
|
106
|
+
r_zip=zm.zip7(f_test,force_pwd=True)
|
|
107
|
+
assert len(r_zip)==3
|
|
108
|
+
assert r_zip[0] == "ok"
|
|
109
|
+
f_zip = r_zip[1]
|
|
110
|
+
pwd = r_zip[2]
|
|
111
|
+
# r_list = zm.unzip7_list_archive(f_zip)
|
|
112
|
+
# assert r_list[0] == "ok"
|
|
113
|
+
r_unzip = zm.unzip7(f_zip,dir_test_out,pwd=pwd)
|
|
114
|
+
assert r_unzip[0] == "ok"
|
|
115
|
+
r_cmp = zm.filecmp(f_test,f_test_out)
|
|
116
|
+
assert r_cmp is True
|
|
117
|
+
if os.path.exists(f_zip):
|
|
118
|
+
os.unlink(f_zip)
|
|
119
|
+
if os.path.exists(f_test_out):
|
|
120
|
+
os.unlink(f_test_out)
|
|
121
|
+
|
|
122
|
+
def test_zip7_mix():
|
|
123
|
+
parent_dir = op.realpath(op.dirname(__file__))
|
|
124
|
+
f_test = op.join(parent_dir, "tmp","test.txt")
|
|
125
|
+
f_test_7z = op.join(parent_dir, "tmp","test.txt.7z")
|
|
126
|
+
if os.path.exists(f_test_7z):
|
|
127
|
+
os.unlink(f_test_7z)
|
|
128
|
+
dir_test_out = op.join(parent_dir, "tmp","out")
|
|
129
|
+
f_test_out = op.join(parent_dir, "tmp","out","test.txt")
|
|
130
|
+
zm = ZipMix()
|
|
131
|
+
r_zip=zm.zip7mix(f_test)
|
|
132
|
+
assert len(r_zip)==2
|
|
133
|
+
assert r_zip[0] == "ok"
|
|
134
|
+
f_zipmix = r_zip[1]
|
|
135
|
+
r_unzip = zm.unzip7mix(f_zipmix,dir_test_out)
|
|
136
|
+
assert r_unzip[0] == "ok"
|
|
137
|
+
r_cmp = zm.filecmp(f_test,f_test_out)
|
|
138
|
+
assert r_cmp is True
|
|
139
|
+
if os.path.exists(f_zipmix):
|
|
140
|
+
os.unlink(f_zipmix)
|
|
141
|
+
if os.path.exists(f_test_out):
|
|
142
|
+
os.unlink(f_test_out)
|
|
Binary file
|