oat-py 1.0.0__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.
- oat_py-1.0.0/PKG-INFO +7 -0
- oat_py-1.0.0/README.md +330 -0
- oat_py-1.0.0/pyproject.toml +23 -0
- oat_py-1.0.0/setup.cfg +4 -0
- oat_py-1.0.0/src/oat/__init__.py +4 -0
- oat_py-1.0.0/src/oat/__main__.py +40 -0
- oat_py-1.0.0/src/oat/analysis/__init__.py +0 -0
- oat_py-1.0.0/src/oat/analysis/document.py +36 -0
- oat_py-1.0.0/src/oat/analysis/file_type.py +136 -0
- oat_py-1.0.0/src/oat/analysis/header_reader.py +114 -0
- oat_py-1.0.0/src/oat/analysis/pipeline.py +122 -0
- oat_py-1.0.0/src/oat/analysis/policy_verifier.py +396 -0
- oat_py-1.0.0/src/oat/cli.py +100 -0
- oat_py-1.0.0/src/oat/config/__init__.py +0 -0
- oat_py-1.0.0/src/oat/config/loader.py +389 -0
- oat_py-1.0.0/src/oat/config/schema.py +113 -0
- oat_py-1.0.0/src/oat/matchers/__init__.py +0 -0
- oat_py-1.0.0/src/oat/matchers/copyright_matcher.py +111 -0
- oat_py-1.0.0/src/oat/matchers/license_matcher.py +472 -0
- oat_py-1.0.0/src/oat/matchers/spdx_license_loader.py +67 -0
- oat_py-1.0.0/src/oat/reporter/__init__.py +0 -0
- oat_py-1.0.0/src/oat/reporter/model.py +33 -0
- oat_py-1.0.0/src/oat/reporter/plain_reporter.py +94 -0
- oat_py-1.0.0/src/oat/resources/OAT-Default.xml +138 -0
- oat_py-1.0.0/src/oat/resources/__init__.py +0 -0
- oat_py-1.0.0/src/oat/resources/builtin_licenses.json +532 -0
- oat_py-1.0.0/src/oat/resources/licenses-exception.json +1 -0
- oat_py-1.0.0/src/oat/resources/licenses.json +9804 -0
- oat_py-1.0.0/src/oat/utils/__init__.py +0 -0
- oat_py-1.0.0/src/oat/utils/text_util.py +37 -0
- oat_py-1.0.0/src/oat/walker/__init__.py +0 -0
- oat_py-1.0.0/src/oat/walker/directory_walker.py +160 -0
- oat_py-1.0.0/src/oat_py.egg-info/PKG-INFO +7 -0
- oat_py-1.0.0/src/oat_py.egg-info/SOURCES.txt +36 -0
- oat_py-1.0.0/src/oat_py.egg-info/dependency_links.txt +1 -0
- oat_py-1.0.0/src/oat_py.egg-info/entry_points.txt +2 -0
- oat_py-1.0.0/src/oat_py.egg-info/requires.txt +2 -0
- oat_py-1.0.0/src/oat_py.egg-info/top_level.txt +1 -0
oat_py-1.0.0/PKG-INFO
ADDED
oat_py-1.0.0/README.md
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# OAT Python Edition
|
|
2
|
+
|
|
3
|
+
> OpenHarmony OSS Audit Tool —— Python 重写版,无需 Java/JDK 环境,开箱即用。
|
|
4
|
+
|
|
5
|
+
[](https://www.python.org/)
|
|
6
|
+
[](../tools_oat/LICENSE)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ✨ 特性
|
|
11
|
+
|
|
12
|
+
| 特性 | 说明 |
|
|
13
|
+
|------|------|
|
|
14
|
+
| **零 Java 依赖** | 仅需 Python 3.9+,告别 JDK/Maven 安装烦恼 |
|
|
15
|
+
| **高性能** | 并发扫描,速度比 Java 版快约 25 倍(143 个文件 0.6s) |
|
|
16
|
+
| **完全兼容** | 读取项目 `OAT.xml`,规则与原版 Java OAT 对齐 |
|
|
17
|
+
| **增量扫描** | 支持只扫描变更文件,适配 pre-commit 工作流 |
|
|
18
|
+
| **报告对齐** | 输出 `PlainReport_*.txt`,格式与原版完全一致 |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📦 安装
|
|
23
|
+
|
|
24
|
+
### 方式一:开发模式安装(推荐)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install -e /path/to/oat_python
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
安装后可直接使用 `oat` 命令。
|
|
31
|
+
|
|
32
|
+
### 方式二:直接运行(无需安装)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cd /path/to/oat_python
|
|
36
|
+
PYTHONPATH=src python -m oat [参数]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 依赖
|
|
40
|
+
|
|
41
|
+
| 包 | 版本 | 用途 |
|
|
42
|
+
|----|------|------|
|
|
43
|
+
| `lxml` | >=5.0 | 解析 OAT.xml 配置文件 |
|
|
44
|
+
| `chardet` | >=5.0 | 源文件编码自动检测 |
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install lxml chardet
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 🚀 快速开始
|
|
53
|
+
|
|
54
|
+
### 全量扫描
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
oat -mode s \
|
|
58
|
+
-s /path/to/your/repo \
|
|
59
|
+
-r /path/to/reports \
|
|
60
|
+
-n YourRepoName
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 增量扫描(pre-commit 场景)
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
oat -mode s \
|
|
67
|
+
-s /path/to/your/repo \
|
|
68
|
+
-r /path/to/reports \
|
|
69
|
+
-n YourRepoName \
|
|
70
|
+
-w 1 \
|
|
71
|
+
-f "src/foo.cpp,include/bar.h"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 查看报告
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
cat /path/to/reports/PlainReport_YourRepoName.txt
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## ⚙️ 命令行参数
|
|
83
|
+
|
|
84
|
+
| 参数 | 必填 | 说明 |
|
|
85
|
+
|------|------|------|
|
|
86
|
+
| `-mode s` | 是 | 运行模式,当前支持 `s`(单项目模式) |
|
|
87
|
+
| `-s SOURCE_DIR` | 是 | 被扫描的源码根目录 |
|
|
88
|
+
| `-r REPORT_DIR` | 是 | 报告输出目录(不存在时自动创建) |
|
|
89
|
+
| `-n REPO_NAME` | 是 | 项目名称,用于报告文件命名 |
|
|
90
|
+
| `-w 0\|1` | 否 | 扫描模式:`0`=全量(默认),`1`=增量 |
|
|
91
|
+
| `-f FILE_LIST` | 否 | 增量扫描文件列表,逗号分隔(`-w 1` 时使用) |
|
|
92
|
+
| `-oatconfig PATH` | 否 | 指定自定义 `OAT-Default.xml` 路径(默认使用内置) |
|
|
93
|
+
| `-ignorePrjOAT` | 否 | 忽略项目根目录下的 `OAT.xml` |
|
|
94
|
+
| `-policy STRING` | 否 | 命令行直接覆盖 policy 规则(见下方说明) |
|
|
95
|
+
| `-filter STRING` | 否 | 命令行直接覆盖 filter 规则(见下方说明) |
|
|
96
|
+
| `-v` | 否 | 打印版本号并退出 |
|
|
97
|
+
|
|
98
|
+
### `-policy` 格式
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
type:name[@path][~rule][|name2[@path2]] 多个 type 用 ; 分隔
|
|
102
|
+
|
|
103
|
+
示例:
|
|
104
|
+
-policy "license:Apache-2.0@.*|MIT@third_party/.*;copyright:Huawei@.*"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `-filter` 格式
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
type:pattern[|type:pattern2 ...]
|
|
111
|
+
|
|
112
|
+
示例:
|
|
113
|
+
-filter "filename:*.pb.h|filename:*.pb.cc|filepath:build/.*"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 📋 规则配置(OAT.xml)
|
|
119
|
+
|
|
120
|
+
工具会按以下优先级合并配置:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
内置 OAT-Default.xml(全局默认规则)
|
|
124
|
+
↓ 合并
|
|
125
|
+
{-s 目录}/OAT.xml(项目自定义规则,可选)
|
|
126
|
+
↓ 合并
|
|
127
|
+
命令行 -policy / -filter(最高优先级)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 在被扫描项目中创建 OAT.xml
|
|
131
|
+
|
|
132
|
+
将 `OAT.xml` 放在被扫描项目的**根目录**,工具会自动加载:
|
|
133
|
+
|
|
134
|
+
```xml
|
|
135
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
136
|
+
<configuration>
|
|
137
|
+
<oatconfig>
|
|
138
|
+
|
|
139
|
+
<!-- 自定义 policy:在默认规则基础上追加 -->
|
|
140
|
+
<policylist>
|
|
141
|
+
<policy name="projectPolicy" desc="">
|
|
142
|
+
<!-- 允许 third_party 目录使用 MIT -->
|
|
143
|
+
<policyitem type="license" name="MIT" path="third_party/.*"
|
|
144
|
+
desc="Third-party MIT licensed code"/>
|
|
145
|
+
<!-- 允许 third_party 目录使用 BSD-3-Clause -->
|
|
146
|
+
<policyitem type="license" name="BSD-3-Clause" path="third_party/.*"
|
|
147
|
+
desc="Third-party BSD licensed code"/>
|
|
148
|
+
<!-- 排除自动生成文件的 copyright 检查 -->
|
|
149
|
+
<policyitem type="copyright" name="*" path="!generated/.*"
|
|
150
|
+
rule="may" desc="Skip generated files"/>
|
|
151
|
+
</policy>
|
|
152
|
+
</policylist>
|
|
153
|
+
|
|
154
|
+
<!-- 自定义 filter:补充不需要扫描的文件 -->
|
|
155
|
+
<filefilterlist>
|
|
156
|
+
<filefilter name="defaultFilter" desc="Project-specific excludes">
|
|
157
|
+
<filteritem type="filepath" name="build/.*" desc="Build output"/>
|
|
158
|
+
<filteritem type="filepath" name="cmake-build/.*" desc="CMake output"/>
|
|
159
|
+
<filteritem type="filename" name="*.pb.h|*.pb.cc" desc="Protobuf generated"/>
|
|
160
|
+
</filefilter>
|
|
161
|
+
</filefilterlist>
|
|
162
|
+
|
|
163
|
+
<!-- 自定义 license 识别规则(识别非 SPDX 标准 license 文本) -->
|
|
164
|
+
<licensematcherlist>
|
|
165
|
+
<licensematcher name="Huawei-Internal">
|
|
166
|
+
<licensetext name="Huawei Proprietary and Confidential"/>
|
|
167
|
+
</licensematcher>
|
|
168
|
+
</licensematcherlist>
|
|
169
|
+
|
|
170
|
+
</oatconfig>
|
|
171
|
+
</configuration>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### OAT.xml 配置项说明
|
|
175
|
+
|
|
176
|
+
#### `<policyitem>` 字段
|
|
177
|
+
|
|
178
|
+
| 字段 | 必填 | 说明 |
|
|
179
|
+
|------|------|------|
|
|
180
|
+
| `type` | 是 | `license`/`copyright`/`filetype`/`filename`/`compatibility` |
|
|
181
|
+
| `name` | 是 | 匹配值;`*` 匹配所有;`!xxx` 表示"不允许 xxx" |
|
|
182
|
+
| `path` | 是 | 文件路径正则;`!` 前缀表示排除,如 `!test/.*` |
|
|
183
|
+
| `rule` | 否 | `may`(默认,组内任意一条通过即可)或 `must`(必须通过) |
|
|
184
|
+
| `group` | 否 | 同组的 `may` 条目取 OR 逻辑,默认 `defaultGroup` |
|
|
185
|
+
| `filefilter` | 否 | 绑定 filter 名称,默认 `defaultPolicyFilter` |
|
|
186
|
+
| `desc` | 是 | 说明该规则的原因(审查时必填) |
|
|
187
|
+
|
|
188
|
+
#### `<filteritem>` 字段
|
|
189
|
+
|
|
190
|
+
| 字段 | 说明 |
|
|
191
|
+
|------|------|
|
|
192
|
+
| `type="filename"` | 按文件名过滤,支持 `*` 通配符,多个用 `\|` 分隔 |
|
|
193
|
+
| `type="filepath"` | 按文件路径过滤,支持正则表达式 |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 📁 项目结构
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
oat_python/
|
|
201
|
+
├── pyproject.toml # 包配置(pip install)
|
|
202
|
+
└── src/
|
|
203
|
+
└── oat/
|
|
204
|
+
├── __init__.py
|
|
205
|
+
├── __main__.py # 入口:python -m oat
|
|
206
|
+
├── cli.py # 命令行解析(argparse)
|
|
207
|
+
├── config/
|
|
208
|
+
│ ├── schema.py # 数据类:OatConfig / Policy / FileFilter / Project
|
|
209
|
+
│ └── loader.py # 加载 OAT-Default.xml + OAT.xml + CLI 覆盖
|
|
210
|
+
├── walker/
|
|
211
|
+
│ └── directory_walker.py # 目录遍历 + 文件过滤
|
|
212
|
+
├── matchers/
|
|
213
|
+
│ ├── license_matcher.py # SPDX / 全文 / 关键词 license 匹配
|
|
214
|
+
│ └── copyright_matcher.py # Copyright 行提取
|
|
215
|
+
├── analysis/
|
|
216
|
+
│ ├── document.py # FileDocument / Issue 数据类
|
|
217
|
+
│ ├── file_type.py # 二进制 / 归档文件检测
|
|
218
|
+
│ ├── header_reader.py # 读取文件头,驱动 matchers
|
|
219
|
+
│ ├── policy_verifier.py # 对照 policy 校验检测结果
|
|
220
|
+
│ └── pipeline.py # 并发扫描调度(ThreadPoolExecutor)
|
|
221
|
+
├── reporter/
|
|
222
|
+
│ ├── model.py # 报告数据结构
|
|
223
|
+
│ └── plain_reporter.py # 生成 PlainReport_*.txt
|
|
224
|
+
├── resources/
|
|
225
|
+
│ ├── OAT-Default.xml # 内置全局默认规则
|
|
226
|
+
│ └── builtin_licenses.json # 50+ 种 license 内置匹配规则
|
|
227
|
+
└── utils/
|
|
228
|
+
└── text_util.py # 文本清洗工具(对齐 Java OatLicenseTextUtil)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 📊 报告格式
|
|
234
|
+
|
|
235
|
+
报告输出到 `-r` 指定目录,文件名为 `PlainReport_{-n}.txt`,格式与原版 Java OAT 对齐:
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
OAT Scan Report
|
|
239
|
+
===============
|
|
240
|
+
Project : YourRepoName
|
|
241
|
+
ScanDate: 2026-04-09 10:00:00
|
|
242
|
+
ScanDir : /path/to/your/repo
|
|
243
|
+
Files : 120
|
|
244
|
+
Issues : 3
|
|
245
|
+
|
|
246
|
+
[License Header Invalid]
|
|
247
|
+
File: src/foo.cpp
|
|
248
|
+
License: InvalidLicense
|
|
249
|
+
...
|
|
250
|
+
|
|
251
|
+
[Copyright Header Invalid]
|
|
252
|
+
File: src/bar.cpp
|
|
253
|
+
Copyright: (none)
|
|
254
|
+
...
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 🔄 与原版 Java OAT 对比
|
|
260
|
+
|
|
261
|
+
| 特性 | Java 版 | Python 版 |
|
|
262
|
+
|------|---------|-----------|
|
|
263
|
+
| 运行环境 | JDK 8+ | Python 3.9+ |
|
|
264
|
+
| 安装方式 | Maven 构建 / 预编译 jar | `pip install` |
|
|
265
|
+
| 扫描速度(143 个文件) | ~15s | **~0.6s** |
|
|
266
|
+
| 项目 OAT.xml 支持 | ✅ | ✅ |
|
|
267
|
+
| OAT-Default.xml 可替换 | ✅(`-oatconfig`) | ✅(`-oatconfig`) |
|
|
268
|
+
| 命令行 policy 覆盖 | ✅ | ✅ |
|
|
269
|
+
| 增量扫描(`-w 1 -f`) | ✅ | ✅ |
|
|
270
|
+
| SPDX license 识别 | ✅ | ✅ |
|
|
271
|
+
| 自定义 licensematcher | ✅ | ✅ |
|
|
272
|
+
| 多项目模式(`-mode p`) | ✅ | ❌(计划支持) |
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## 🔧 集成到 pre-commit
|
|
277
|
+
|
|
278
|
+
在被扫描项目的 `.pre-commit-config.yaml` 中添加:
|
|
279
|
+
|
|
280
|
+
```yaml
|
|
281
|
+
repos:
|
|
282
|
+
- repo: local
|
|
283
|
+
hooks:
|
|
284
|
+
- id: oat-check
|
|
285
|
+
name: OAT License Check
|
|
286
|
+
language: python
|
|
287
|
+
entry: python -m oat
|
|
288
|
+
args:
|
|
289
|
+
- -mode
|
|
290
|
+
- s
|
|
291
|
+
- -s
|
|
292
|
+
- .
|
|
293
|
+
- -r
|
|
294
|
+
- oat_reports/single
|
|
295
|
+
- -n
|
|
296
|
+
- YourRepoName
|
|
297
|
+
- -w
|
|
298
|
+
- "1"
|
|
299
|
+
pass_filenames: false
|
|
300
|
+
additional_dependencies:
|
|
301
|
+
- lxml>=5.0
|
|
302
|
+
- chardet>=5.0
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## ❓ 常见问题
|
|
308
|
+
|
|
309
|
+
**Q: 如何自定义 license 检查规则?**
|
|
310
|
+
A: 在项目根目录创建 `OAT.xml`,添加 `<policylist>` 配置,工具自动读取。
|
|
311
|
+
|
|
312
|
+
**Q: 某些文件不需要检查,如何排除?**
|
|
313
|
+
A: 在 `OAT.xml` 的 `<filefilterlist>` 中添加 `<filteritem>`,支持文件名 glob 和路径正则。
|
|
314
|
+
|
|
315
|
+
**Q: 如何识别项目中的自定义 license 文本?**
|
|
316
|
+
A: 在 `OAT.xml` 的 `<licensematcherlist>` 中添加 `<licensetext>`,工具会将匹配到的文本映射为指定 license 名称。
|
|
317
|
+
|
|
318
|
+
**Q: 运行时出现 `ModuleNotFoundError: No module named 'oat'`?**
|
|
319
|
+
A: 使用安装模式:`pip install -e /path/to/oat_python`,或设置 `PYTHONPATH=src`。
|
|
320
|
+
|
|
321
|
+
**Q: 如何跳过项目 OAT.xml 只用全局规则?**
|
|
322
|
+
A: 添加 `-ignorePrjOAT` 参数。
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 📞 参考
|
|
327
|
+
|
|
328
|
+
- **原版 Java OAT**: https://gitcode.com/openharmony-sig/tools_oat
|
|
329
|
+
- **SPDX License List**: https://spdx.org/licenses/
|
|
330
|
+
- **OpenHarmony 合规指南**: https://www.openharmony.cn/
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "oat-py"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "OSS Audit Tool - Python Edition"
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"lxml>=5.0",
|
|
12
|
+
"chardet>=5.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
oat = "oat.__main__:main"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools.packages.find]
|
|
19
|
+
where = ["src"]
|
|
20
|
+
include = ["oat*"]
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.package-data]
|
|
23
|
+
"oat.resources" = ["*.json", "*.xml"]
|
oat_py-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Entry point: python -m oat or oat (if installed via pip)
|
|
3
|
+
"""
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main(argv=None):
|
|
10
|
+
from oat.cli import parse_args
|
|
11
|
+
args = parse_args(argv)
|
|
12
|
+
|
|
13
|
+
start = time.time()
|
|
14
|
+
print(f"[OAT] OpenHarmony OSS Audit Tool - Python Edition")
|
|
15
|
+
print(f"[OAT] Scanning: {args.s}")
|
|
16
|
+
|
|
17
|
+
from oat.config.loader import build_task
|
|
18
|
+
from oat.analysis.pipeline import run_pipeline
|
|
19
|
+
from oat.reporter.plain_reporter import PlainReporter
|
|
20
|
+
|
|
21
|
+
task = build_task(args)
|
|
22
|
+
|
|
23
|
+
report_model = run_pipeline(task)
|
|
24
|
+
|
|
25
|
+
reporter = PlainReporter(task, report_model)
|
|
26
|
+
reporter.write()
|
|
27
|
+
|
|
28
|
+
elapsed = time.time() - start
|
|
29
|
+
total = report_model.total_issues
|
|
30
|
+
print(f"[OAT] Scan complete in {elapsed:.1f}s. Issues found: {total}")
|
|
31
|
+
if total > 0:
|
|
32
|
+
print(f"[OAT] Report: {task.report_dir}")
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
else:
|
|
35
|
+
print("[OAT] No compliance issues found.")
|
|
36
|
+
sys.exit(0)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FileDocument — mirrors OatFileDocument.java.
|
|
3
|
+
Holds analysis results for a single file.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class FileDocument:
|
|
14
|
+
path: Path
|
|
15
|
+
rel_path: str = "" # relative to src_dir, forward slashes
|
|
16
|
+
|
|
17
|
+
# Set by file_type analyser
|
|
18
|
+
file_type: str = "text" # 'text' | 'binary' | 'archive'
|
|
19
|
+
|
|
20
|
+
# Set by header analyser
|
|
21
|
+
license: str = "NoLicenseHeader"
|
|
22
|
+
copyright_owners: List[str] = field(default_factory=list)
|
|
23
|
+
|
|
24
|
+
# Set by policy_verifier
|
|
25
|
+
issues: List["Issue"] = field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def has_issues(self) -> bool:
|
|
29
|
+
return bool(self.issues)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class Issue:
|
|
34
|
+
issue_type: str # 'filetype' | 'license' | 'copyright' | 'filename'
|
|
35
|
+
description: str
|
|
36
|
+
detail: str = ""
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File type analyser — mirrors OatFileTypeAnalyser.java + Apache Rat BinaryGuesser.
|
|
3
|
+
Detects binary and archive files.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
# Archive file extensions (lowercase)
|
|
10
|
+
_ARCHIVE_EXTS = {
|
|
11
|
+
".jar", ".gz", ".zip", ".tar", ".bz2", ".rar", ".war", ".7z",
|
|
12
|
+
".rpm", ".deb", ".img", ".apk", ".ipa", ".whl", ".egg",
|
|
13
|
+
".tar.gz", ".tar.bz2", ".tar.xz", ".tgz", ".tbz2",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# Binary/pre-built file extensions (lowercase).
|
|
17
|
+
# Mirrors OatFileUtils.PREBUILD_FILE_EXTENSION + Apache Rat BinaryGuesser extension lists.
|
|
18
|
+
_BINARY_EXTS = {
|
|
19
|
+
".so", ".dll", ".exe", ".elf", ".bin", ".a", ".o", ".class",
|
|
20
|
+
".pyc", ".pyd", ".pyo", ".hap", ".scr", ".lib", ".pdb",
|
|
21
|
+
".obj", ".ko", ".d.ts", ".exp",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Keystore / certificate binary extensions from Apache Rat BinaryGuesser.KEYSTORE_EXTENSIONS.
|
|
25
|
+
# These are always treated as binary regardless of content.
|
|
26
|
+
_KEYSTORE_EXTS = {
|
|
27
|
+
".jks", ".keystore", ".pem", ".crl", ".truststore", ".cert", ".ks",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Image extensions from Apache Rat BinaryGuesser.IMAGE_EXTENSIONS.
|
|
31
|
+
_IMAGE_EXTS = {
|
|
32
|
+
".png", ".pdf", ".gif", ".giff", ".tif", ".tiff", ".jpg", ".jpeg",
|
|
33
|
+
".ico", ".icns", ".psd", ".webp", ".bmp", ".svg",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Audio / media extensions from Apache Rat BinaryGuesser.AUDIO_EXTENSIONS.
|
|
37
|
+
_AUDIO_EXTS = {
|
|
38
|
+
".aif", ".iff", ".m3u", ".mid", ".mp3", ".mpa", ".wav", ".wma",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Font / layout binary extensions
|
|
42
|
+
_FONT_EXTS = {
|
|
43
|
+
".woff", ".woff2", ".ttf", ".eot", ".otf",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Extensions that Apache Rat BinaryGuesser.NON_BINARY_EXTENSIONS explicitly lists as text.
|
|
47
|
+
# Files with these extensions are NEVER treated as binary by content-sniffing.
|
|
48
|
+
_NON_BINARY_EXTS = {
|
|
49
|
+
".ac", ".am", ".bat", ".cat", ".cgi", ".classpath", ".cmd", ".config",
|
|
50
|
+
".cpp", ".css", ".cwiki", ".data", ".dcl", ".dtd", ".egrm", ".ent",
|
|
51
|
+
".ft", ".fn", ".fv", ".grm", ".go", ".htaccess", ".html", ".ihtml",
|
|
52
|
+
".in", ".jmx", ".jsp", ".js", ".json", ".junit", ".jx",
|
|
53
|
+
".manifest", ".md", ".mf", ".meta", ".mod",
|
|
54
|
+
".pen", ".pl", ".pm", ".pod", ".pom", ".project", ".properties",
|
|
55
|
+
".py", ".rb", ".rdf", ".rnc", ".rng", ".rnx", ".roles", ".rss",
|
|
56
|
+
".sh", ".sql", ".svg", ".tld", ".txt", ".types",
|
|
57
|
+
".vm", ".vsl", ".wsdd", ".wsdl", ".xargs", ".xcat", ".xconf",
|
|
58
|
+
".xegrm", ".xgrm", ".xlex", ".xlog", ".xmap", ".xml",
|
|
59
|
+
".xroles", ".xsamples", ".xsd", ".xsl", ".xslt", ".xsp", ".xul",
|
|
60
|
+
".xweb", ".xwelcome",
|
|
61
|
+
# Additional common text extensions not in Apache Rat 0.13 but treated as text by OAT
|
|
62
|
+
".c", ".h", ".cc", ".hh", ".java", ".ts", ".cs", ".rb", ".php",
|
|
63
|
+
".yml", ".yaml", ".toml", ".rst", ".tex",
|
|
64
|
+
".gradle", ".cmake", ".mk", ".makefile",
|
|
65
|
+
".proto", ".thrift", ".avro",
|
|
66
|
+
".ini", ".cfg", ".conf",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Probe this many bytes for content-based binary detection
|
|
70
|
+
_PROBE_BYTES = 512
|
|
71
|
+
|
|
72
|
+
# Apache Rat HIGH_BYTES_RATIO threshold: if >30% of chars are non-ASCII → binary
|
|
73
|
+
_HIGH_BYTE_THRESHOLD = 30
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def is_archive(path: Path) -> bool:
|
|
77
|
+
"""Return True if the file is a known archive type."""
|
|
78
|
+
name = path.name.lower()
|
|
79
|
+
# Check compound extensions first
|
|
80
|
+
for ext in (".tar.gz", ".tar.bz2", ".tar.xz", ".so.gz"):
|
|
81
|
+
if name.endswith(ext):
|
|
82
|
+
return True
|
|
83
|
+
return path.suffix.lower() in _ARCHIVE_EXTS
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def is_binary(path: Path) -> bool:
|
|
87
|
+
"""Return True if the file should be treated as binary.
|
|
88
|
+
|
|
89
|
+
Mirrors Apache Rat BinaryGuesser.isBinary() logic used by OAT Java:
|
|
90
|
+
1. Known binary extensions → binary
|
|
91
|
+
2. Known keystore/image/audio/font extensions → binary
|
|
92
|
+
3. Explicitly listed non-binary extensions → NOT binary (skip content check)
|
|
93
|
+
4. Content check: if >30% of bytes in first 512 bytes are >127 → binary
|
|
94
|
+
5. Null byte in first 512 bytes → binary
|
|
95
|
+
"""
|
|
96
|
+
ext = path.suffix.lower()
|
|
97
|
+
name_lower = path.name.lower()
|
|
98
|
+
|
|
99
|
+
# Step 1: known binary extensions
|
|
100
|
+
if ext in _BINARY_EXTS:
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
# Step 2: keystore / image / audio / font → binary
|
|
104
|
+
if ext in _KEYSTORE_EXTS or ext in _IMAGE_EXTS or ext in _AUDIO_EXTS or ext in _FONT_EXTS:
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
# Step 3: explicitly non-binary extension → skip content check
|
|
108
|
+
if ext in _NON_BINARY_EXTS:
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
# Step 4+5: content-based detection
|
|
112
|
+
try:
|
|
113
|
+
with open(path, "rb") as f:
|
|
114
|
+
chunk = f.read(_PROBE_BYTES)
|
|
115
|
+
if not chunk:
|
|
116
|
+
return False
|
|
117
|
+
# Null byte → binary
|
|
118
|
+
if b"\x00" in chunk:
|
|
119
|
+
return True
|
|
120
|
+
# High-byte ratio check (mirrors Apache Rat BinaryGuesser HIGH_BYTES_RATIO=30)
|
|
121
|
+
high = sum(1 for b in chunk if b > 127)
|
|
122
|
+
if high * 100 // len(chunk) > _HIGH_BYTE_THRESHOLD:
|
|
123
|
+
return True
|
|
124
|
+
except OSError:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_file_type(path: Path) -> str:
|
|
131
|
+
"""Return 'archive', 'binary', or 'text'."""
|
|
132
|
+
if is_archive(path):
|
|
133
|
+
return "archive"
|
|
134
|
+
if is_binary(path):
|
|
135
|
+
return "binary"
|
|
136
|
+
return "text"
|