import source

这个提交包含在:
JGZYES
2026-09-02 21:18:36 +08:00
父节点 22a689ebf7
当前提交 843e9eea5b
修改 106 个文件,包含 35298 行新增243 行删除
+3
查看文件
@@ -27,3 +27,6 @@
!web/downloads/
!web/downloads/**/*.zip
!web/downloads/**/*.deb
# source browser cache clone
web/.src/
+1
查看文件
@@ -0,0 +1 @@
{"message": "mirror: pmm -> 0.1.5 (strncat fix)", "content": "ew0KICAibmFtZSI6ICJwbW0iLA0KICAidmVyc2lvbiI6ICIwLjEuNSIsDQogICJvcyI6ICJ3aW5kb3dzIiwNCiAgImFyY2giOiAiYW1kNjQiLA0KICAiZGVzY3JpcHRpb24iOiAiUE1NIHYwLjEuNSIsDQogICJ2YXJpYW50cyI6IFsNCiAgICB7DQogICAgICAidmVyc2lvbiI6ICIwLjEuNSIsDQogICAgICAib3MiOiAid2luZG93cyIsDQogICAgICAiYXJjaCI6ICJhbWQ2NCIsDQogICAgICAiZmlsZSI6ICIwLjEuNS13aW5kb3dzLWFtZDY0LnBkbSIsDQogICAgICAidXJsIjogImh0dHBzOi8vcG1tLnBhcmx6LmNvbS9taXJyb3IvcGFja2FnZXMvcG1tLzAuMS41LXdpbmRvd3MtYW1kNjQucGRtIiwNCiAgICAgICJzaGEyNTYiOiAiYWU1YWRiZjY3MjFkZGM2NzM0MjEwN2Q2ZjQ5ZDZmZTY5N2E1NzQyNjZjYjA2ZjQ2YmRkMjEzZDFhMzFmN2E4YSIsDQogICAgICAiZGVzY3JpcHRpb24iOiAiUE1NIHYwLjEuNSAoc3RybmNhdCBmaXgpIg0KICAgIH0sDQogICAgew0KICAgICAgInZlcnNpb24iOiAiMC4xLjUiLA0KICAgICAgIm9zIjogImxpbnV4IiwNCiAgICAgICJhcmNoIjogImFtZDY0IiwNCiAgICAgICJmaWxlIjogIjAuMS41LWxpbnV4LWFtZDY0LnBkbSIsDQogICAgICAidXJsIjogImh0dHBzOi8vcG1tLnBhcmx6LmNvbS9taXJyb3IvcGFja2FnZXMvcG1tLzAuMS41LWxpbnV4LWFtZDY0LnBkbSIsDQogICAgICAic2hhMjU2IjogIjhiZTkzMTQ0N2I2NjRhNDY3ZWJiYjE0MjAwOGE4N2ZmYzdlMmYzMGVhNmEyZTBkODk4MDMyNWIwNGYyM2JjYmMiLA0KICAgICAgImRlc2NyaXB0aW9uIjogIlBNTSB2MC4xLjUgKHN0cm5jYXQgZml4KSINCiAgICB9DQogIF0NCn0=", "sha": "{\"message\":\"Not Found\",\"documentation_url\":\"https://docs.github.com/rest/repos/contents#get-repository-content\",\"status\":\"404\"}"}
+1
查看文件
@@ -0,0 +1 @@
{"message": "mirror: pmm -> 0.1.5 (strncat fix)", "content": "ew0KICAibmFtZSI6ICJwbW0iLA0KICAidmVyc2lvbiI6ICIwLjEuNSIsDQogICJmaWxlIjogIjAuMS41LXdpbmRvd3MtYW1kNjQucGRtIiwNCiAgIm9zIjogIndpbmRvd3MiLA0KICAiYXJjaCI6ICJhbWQ2NCIsDQogICJ1cmwiOiAiaHR0cHM6Ly9wbW0ucGFybHouY29tL21pcnJvci9wYWNrYWdlcy9wbW0vMC4xLjUtd2luZG93cy1hbWQ2NC5wZG0iLA0KICAic2hhMjU2IjogImFlNWFkYmY2NzIxZGRjNjczNDIxMDdkNmY0OWQ2ZmU2OTdhNTc0MjY2Y2IwNmY0NmJkZDIxM2QxYTMxZjdhOGEiLA0KICAiZGVzY3JpcHRpb24iOiAiUE1NIHYwLjEuNSAoc3RybmNhdCBmaXgpIg0KfQ==", "sha": "{\"message\":\"Not Found\",\"documentation_url\":\"https://docs.github.com/rest/repos/contents#get-repository-content\",\"status\":\"404\"}"}
+1
查看文件
@@ -0,0 +1 @@
{"message": "mirror: pmm -> 0.1.5 (strncat fix)", "content": "ew0KICAibmFtZSI6ICJwbW0iLA0KICAidmVyc2lvbiI6ICIwLjEuNSIsDQogICJmaWxlIjogIjAuMS41LWxpbnV4LWFtZDY0LnBkbSIsDQogICJvcyI6ICJsaW51eCIsDQogICJhcmNoIjogImFtZDY0IiwNCiAgInVybCI6ICJodHRwczovL3BtbS5wYXJsei5jb20vbWlycm9yL3BhY2thZ2VzL3BtbS8wLjEuNS1saW51eC1hbWQ2NC5wZG0iLA0KICAic2hhMjU2IjogIjhiZTkzMTQ0N2I2NjRhNDY3ZWJiYjE0MjAwOGE4N2ZmYzdlMmYzMGVhNmEyZTBkODk4MDMyNWIwNGYyM2JjYmMiLA0KICAiZGVzY3JpcHRpb24iOiAiUE1NIHYwLjEuNSAoc3RybmNhdCBmaXgpIg0KfQ==", "sha": "{\"message\":\"Not Found\",\"documentation_url\":\"https://docs.github.com/rest/repos/contents#get-repository-content\",\"status\":\"404\"}"}
+140
查看文件
@@ -0,0 +1,140 @@
# PHP Project
> A brief description of your PHP project
## Requirements
- **PHP**: >= 8.0 (recommended: PHP 8.2 or 8.3)
- **Extensions**: openssl, pdo_mysql, mbstring, json, curl
- **Database**: MySQL 5.7+ / MariaDB 10.4+ / PostgreSQL 13+
- **Web Server**: Nginx / Apache / IIS (with FastCGI)
- **Composer**: Latest version ([getcomposer.org](https://getcomposer.org))
> For Windows users, consider using WSL2 or local stacks like XAMPP/WampServer/Laragon.
## Installation
1. Clone the repository
git clone https://github.com/yourusername/yourproject.git
cd yourproject
2. Install dependencies via Composer
composer install
3. Environment configuration
cp .env.example .env
Edit .env file with your database credentials and other settings.
4. Generate application key (if using Laravel or similar)
php artisan key:generate
5. Run database migrations (if applicable)
php artisan migrate
## Configuration
Key configuration files:
- `.env` - Environment variables (database, API keys, app settings)
- `config/app.php` - Application-level configuration
- `config/database.php` - Database connection settings
- `php.ini` - PHP runtime settings
PHP INI recommendations for production:
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300
date.timezone = UTC
opcache.enable = 1
opcache.memory_consumption = 128
## Usage
Development server (quick start):
php -S localhost:8000 -t public
Open browser: http://localhost:8000
Production deployment - Nginx:
server {
listen 80;
server_name yourdomain.com;
root /var/www/yourproject/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Production deployment - Apache (.htaccess):
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
</IfModule>
Running scheduled tasks (cron) - Linux:
* * * * * php /path/to/yourproject/artisan schedule:run >> /dev/null 2>&1
## Directory Structure
yourproject/
├── app/ - Application core logic
├── bootstrap/ - Framework bootstrapping
├── config/ - Configuration files
├── database/ - Migrations and seeds
├── public/ - Public web root (entry point)
│ ├── index.php - Front controller
│ └── .htaccess - Apache rewrite rules
├── resources/ - Views, assets, language files
├── routes/ - Route definitions
├── storage/ - Logs, cache, file uploads
├── tests/ - Unit and feature tests
├── vendor/ - Composer dependencies
├── .env - Environment configuration
├── composer.json - Project dependencies
├── composer.lock - Locked dependency versions
└── README.md - This file
## Contributing
1. Fork the repository
2. Create a feature branch: git checkout -b feature/amazing-feature
3. Commit your changes: git commit -m 'Add some amazing feature'
4. Push to the branch: git push origin feature/amazing-feature
5. Open a Pull Request
Coding Standards:
- Follow PSR-12 coding standards
- Write unit tests for new features
- Update documentation accordingly
## License
This project is licensed under the MIT License.
## Acknowledgments
- [PHP](https://php.net)
- [Composer](https://getcomposer.org)
Built with love using PHP
+172
查看文件
@@ -0,0 +1,172 @@
<div align="center">
<a href="https://www.php.net">
<img
alt="PHP"
src="https://www.php.net/images/logos/new-php-logo.svg"
width="150">
</a>
</div>
# The PHP Interpreter
PHP is a popular general-purpose scripting language that is especially suited to
web development. Fast, flexible and pragmatic, PHP powers everything from your
blog to the most popular websites in the world. PHP is distributed under the
[PHP License v3.01](LICENSE).
[![Push](https://github.com/php/php-src/actions/workflows/push.yml/badge.svg)](https://github.com/php/php-src/actions/workflows/push.yml)
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/php.svg)](https://issues.oss-fuzz.com/issues?q=project:php)
## Documentation
The PHP manual is available at [php.net/docs](https://www.php.net/docs).
## Installation
### Prebuilt packages and binaries
Prebuilt packages and binaries can be used to get up and running fast with PHP.
For Windows, the PHP binaries can be obtained from
[windows.php.net](https://windows.php.net). After extracting the archive the
`*.exe` files are ready to use.
For other systems, see the [installation chapter](https://www.php.net/install).
### Building PHP source code
*For Windows, see [Build your own PHP on Windows](https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2).*
For a minimal PHP build from Git, you will need autoconf, bison, and re2c. For
a default build, you will additionally need libxml2 and libsqlite3.
On Ubuntu, you can install these using:
```shell
sudo apt install -y pkg-config build-essential autoconf bison re2c libxml2-dev libsqlite3-dev
```
On Fedora, you can install these using:
```shell
sudo dnf install re2c bison autoconf make libtool ccache libxml2-devel sqlite-devel
```
On MacOS, you can install these using `brew`:
```shell
brew install autoconf bison re2c libiconv libxml2 sqlite
```
or with `MacPorts`:
```shell
sudo port install autoconf bison re2c libiconv libxml2 sqlite3
```
Generate configure:
```shell
./buildconf
```
Configure your build. `--enable-debug` is recommended for development, see
`./configure --help` for a full list of options.
```shell
# For development
./configure --enable-debug
# For production
./configure
```
Build PHP. To speed up the build, specify the maximum number of jobs using the
`-j` argument:
```shell
make -j4
```
The number of jobs should usually match the number of available cores, which
can be determined using `nproc`.
## Testing PHP source code
PHP ships with an extensive test suite, the command `make test` is used after
successful compilation of the sources to run this test suite.
It is possible to run tests using multiple cores by setting `-jN` in
`TEST_PHP_ARGS` or `TESTS`:
```shell
make TEST_PHP_ARGS=-j4 test
```
Shall run `make test` with a maximum of 4 concurrent jobs: Generally the maximum
number of jobs should not exceed the number of cores available.
Use the `TEST_PHP_ARGS` or `TESTS` variable to test only specific directories:
```shell
make TESTS=tests/lang/ test
```
The [qa.php.net](https://qa.php.net) site provides more detailed info about
testing and quality assurance.
## Installing PHP built from source
After a successful build (and test), PHP may be installed with:
```shell
make install
```
Depending on your permissions and prefix, `make install` may need superuser
permissions.
## PHP extensions
Extensions provide additional functionality on top of PHP. PHP consists of many
essential bundled extensions. Additional extensions can be found in the PHP
Extension Community Library - [PECL](https://pecl.php.net).
## Contributing
The PHP source code is located in the Git repository at
[github.com/php/php-src](https://github.com/php/php-src). Contributions are most
welcome by forking the repository and sending a pull request.
Discussions are done on GitHub, but depending on the topic can also be relayed
to the official PHP developer mailing list internals@lists.php.net.
New features require an RFC and must be accepted by the developers. See
[Request for comments - RFC](https://wiki.php.net/rfc) and
[Voting on PHP features](https://wiki.php.net/rfc/voting) for more information
on the process.
Bug fixes don't require an RFC. If the bug has a GitHub issue, reference it in
the commit message using `GH-NNNNNN`. Use `#NNNNNN` for tickets in the old
[bugs.php.net](https://bugs.php.net) bug tracker.
Fix GH-7815: php_uname doesn't recognise latest Windows versions
Fix #55371: get_magic_quotes_gpc() throws deprecation warning
See [Git workflow](https://wiki.php.net/vcs/gitworkflow) for details on how pull
requests are merged.
### Guidelines for contributors
See further documents in the repository for more information on how to
contribute:
- [Contributing to PHP](/CONTRIBUTING.md)
- [PHP coding standards](/CODING_STANDARDS.md)
- [Internal documentation](https://php.github.io/php-src/)
- [Mailing list rules](/docs/mailinglist-rules.md)
- [PHP release process](/docs/release-process.md)
## Credits
For the list of people who've put work into PHP, please see the
[PHP credits page](https://www.php.net/credits.php).
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
文件差异内容过多而无法显示 加载差异
@@ -0,0 +1,590 @@
{
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://php.net/sbom/windows/php/8.5.10/vex/17b9cda9-4457-42ea-a4b1-8bf5d3b29082",
"author": "PHP Group",
"timestamp": "2026-08-25T21:29:48Z",
"version": 1,
"statements": [
{
"vulnerability": {
"name": "CVE-1999-0289"
},
"timestamp": "2026-07-18T08:34:11Z",
"products": [
{
"@id": "pkg:php-windows-deps/apache@2.4.68",
"identifiers": {
"purl": "pkg:generic/apache-httpd@2.4.68"
}
}
],
"status": "not_affected",
"justification": "vulnerable_code_not_present",
"impact_statement": "The affected legacy Win32 path handling is not present in Apache HTTP Server 2.4 builds."
},
{
"vulnerability": {
"name": "CVE-1999-0678"
},
"timestamp": "2026-07-18T08:34:11Z",
"products": [
{
"@id": "pkg:php-windows-deps/apache@2.4.68",
"identifiers": {
"purl": "pkg:generic/apache-httpd@2.4.68"
}
}
],
"status": "not_affected",
"justification": "component_not_present",
"impact_statement": "This CVE concerns a Debian default ServerRoot configuration; Debian packaging and configuration are not present in the Windows artifact."
},
{
"vulnerability": {
"name": "CVE-2025-3891"
},
"timestamp": "2026-07-18T08:34:11Z",
"products": [
{
"@id": "pkg:php-windows-deps/apache@2.4.68",
"identifiers": {
"purl": "pkg:generic/apache-httpd@2.4.68"
}
}
],
"status": "not_affected",
"justification": "component_not_present",
"impact_statement": "This CVE affects mod_auth_openidc, which is a separate module and is not included in the Apache dependency artifact."
},
{
"vulnerability": {
"name": "CVE-2017-8806"
},
"timestamp": "2026-08-15T04:25:38Z",
"products": [
{
"@id": "pkg:php-windows-deps/libpq@16.15",
"identifiers": {
"purl": "pkg:generic/postgresql@16.15"
}
}
],
"status": "not_affected",
"justification": "component_not_present",
"impact_statement": "This CVE affects Debian and Ubuntu postgresql-common cluster scripts, which are not included in the Windows libpq artifact."
},
{
"vulnerability": {
"name": "CVE-2026-7598"
},
"timestamp": "2026-08-02T09:28:50Z",
"products": [
{
"@id": "pkg:php-windows-deps/libssh2@1.11.1-7",
"identifiers": {
"purl": "pkg:generic/libssh2@1.11.1"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting upstream username_len bounds checking in src/userauth.c."
},
{
"vulnerability": {
"name": "CVE-2025-15661"
},
"timestamp": "2026-08-02T09:28:50Z",
"products": [
{
"@id": "pkg:php-windows-deps/libssh2@1.11.1-7",
"identifiers": {
"purl": "pkg:generic/libssh2@1.11.1"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting the upstream bounds-checked parsing for malformed SFTP symlink responses in src/sftp.c and the follow-up SSH_FXP_STATUS response handling fix."
},
{
"vulnerability": {
"name": "CVE-2026-55199"
},
"timestamp": "2026-08-02T09:28:50Z",
"products": [
{
"@id": "pkg:php-windows-deps/libssh2@1.11.1-7",
"identifiers": {
"purl": "pkg:generic/libssh2@1.11.1"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting the upstream parse-failure handling for SSH_MSG_EXT_INFO extension name and value strings in src/packet.c."
},
{
"vulnerability": {
"name": "CVE-2026-55200"
},
"timestamp": "2026-08-02T09:28:50Z",
"products": [
{
"@id": "pkg:php-windows-deps/libssh2@1.11.1-7",
"identifiers": {
"purl": "pkg:generic/libssh2@1.11.1"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting the upstream packet length upper-bound validation in src/transport.c."
},
{
"vulnerability": {
"name": "CVE-2026-58050"
},
"timestamp": "2026-08-02T09:28:50Z",
"products": [
{
"@id": "pkg:php-windows-deps/libssh2@1.11.1-7",
"identifiers": {
"purl": "pkg:generic/libssh2@1.11.1"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting the upstream bounds checking for public-key attribute counts in src/publickey.c."
},
{
"vulnerability": {
"name": "CVE-2026-58051"
},
"timestamp": "2026-08-02T09:28:50Z",
"products": [
{
"@id": "pkg:php-windows-deps/libssh2@1.11.1-7",
"identifiers": {
"purl": "pkg:generic/libssh2@1.11.1"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting the upstream initialization of newly allocated public-key list entries in src/publickey.c."
},
{
"vulnerability": {
"name": "CVE-2026-66032"
},
"timestamp": "2026-08-02T09:28:50Z",
"products": [
{
"@id": "pkg:php-windows-deps/libssh2@1.11.1-7",
"identifiers": {
"purl": "pkg:generic/libssh2@1.11.1"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting the upstream nullification of freed SFTP response data in src/sftp.c."
},
{
"vulnerability": {
"name": "CVE-2026-66033"
},
"timestamp": "2026-08-02T09:28:50Z",
"products": [
{
"@id": "pkg:php-windows-deps/libssh2@1.11.1-7",
"identifiers": {
"purl": "pkg:generic/libssh2@1.11.1"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting the upstream runtime bounds checks for AES-GCM block processing in src/openssl.c."
},
{
"vulnerability": {
"name": "CVE-2026-66034"
},
"timestamp": "2026-08-02T09:28:50Z",
"products": [
{
"@id": "pkg:php-windows-deps/libssh2@1.11.1-7",
"identifiers": {
"purl": "pkg:generic/libssh2@1.11.1"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting the upstream bounds checking for public-key comment lengths in src/publickey.c."
},
{
"vulnerability": {
"name": "CVE-2026-66035"
},
"timestamp": "2026-08-02T09:28:50Z",
"products": [
{
"@id": "pkg:php-windows-deps/libssh2@1.11.1-7",
"identifiers": {
"purl": "pkg:generic/libssh2@1.11.1"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting the upstream lower-bound validation for Encrypt-then-MAC packet decryption in src/transport.c."
},
{
"vulnerability": {
"name": "CVE-2024-56171"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxml2 backport in tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2025-24928"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxml2 backport in tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2025-27113"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by winlibs/libxml2 backport in tag libxml2-2.11.9-1."
},
{
"vulnerability": {
"name": "CVE-2025-32414"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxml2 backport in tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2025-32415"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the xmlSchemaIDCFillNodeTables heap buffer overflow backport in winlibs/libxml2 tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2025-49794"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxml2 schematron backport in tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2025-49795"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxml2 schematron backport in tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2025-49796"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxml2 schematron backport in tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2025-6021"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxml2 backport in tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2025-6170"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the debugXML interactive shell buffer overflow backport in winlibs/libxml2 tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2025-7425"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by preserving libxslt private flag bits in winlibs/libxml2 tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2025-8732"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by SGML catalog recursion limit backport in winlibs/libxml2 tag libxml2-2.11.9-5."
},
{
"vulnerability": {
"name": "CVE-2026-0989"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxml2 backport in tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2026-0990"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxml2 backport in tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2026-0992"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxml2 backport and compatibility follow-up in tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2026-1757"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxml2 backport in tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2023-45322"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting the upstream DTD-copy use-after-free fix in xmlStaticCopyNodeList and its regression follow-up in winlibs/libxml2 tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2026-11979"
},
"timestamp": "2026-07-16T19:59:36Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxml2@2.11.9-7",
"identifiers": {
"purl": "pkg:generic/libxml2@2.11.9"
}
}
],
"status": "fixed",
"action_statement": "Fixed by backporting the upstream bounds checks for xmlcatalog --shell command and argument parsing in winlibs/libxml2 tag libxml2-2.11.9-7."
},
{
"vulnerability": {
"name": "CVE-2025-10911"
},
"timestamp": "2026-07-16T20:01:12Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxslt@1.1.43-2",
"identifiers": {
"purl": "pkg:generic/libxslt@1.1.43"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxslt backport in tag libxslt-1.1.43-2."
},
{
"vulnerability": {
"name": "CVE-2025-11731"
},
"timestamp": "2026-07-16T20:01:12Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxslt@1.1.43-2",
"identifiers": {
"purl": "pkg:generic/libxslt@1.1.43"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxslt backport in tag libxslt-1.1.43-2."
},
{
"vulnerability": {
"name": "CVE-2025-7424"
},
"timestamp": "2026-07-16T20:01:12Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxslt@1.1.43-2",
"identifiers": {
"purl": "pkg:generic/libxslt@1.1.43"
}
}
],
"status": "fixed",
"action_statement": "Fixed by the winlibs/libxslt backport in tag libxslt-1.1.43-2."
},
{
"vulnerability": {
"name": "CVE-2025-7425"
},
"timestamp": "2026-07-16T20:01:12Z",
"products": [
{
"@id": "pkg:php-windows-deps/libxslt@1.1.43-2",
"identifiers": {
"purl": "pkg:generic/libxslt@1.1.43"
}
}
],
"status": "not_affected",
"justification": "inline_mitigations_already_exist",
"impact_statement": "The patched libxml2 dependency shipped with this Winlibs build preserves the private atype flag bits, preventing the corruption in libxslt."
}
]
}
文件差异因一行或多行过长而隐藏
二进制文件未显示。
@@ -0,0 +1,390 @@
#
# OpenSSL example configuration file.
# See doc/man5/config.pod for more info.
#
# This is mostly being used for generation of certificate requests,
# but may be used for auto loading of providers
# Note that you can include other files from the main configuration
# file using the .include directive.
#.include filename
# This definition stops the following lines choking if HOME isn't
# defined.
HOME = .
# Use this in order to automatically load providers.
openssl_conf = openssl_init
# Comment out the next line to ignore configuration errors
config_diagnostics = 1
# Extra OBJECT IDENTIFIER info:
# oid_file = $ENV::HOME/.oid
oid_section = new_oids
# To use this configuration file with the "-extfile" option of the
# "openssl x509" utility, name here the section containing the
# X.509v3 extensions to use:
# extensions =
# (Alternatively, use a configuration file that has only
# X.509v3 extensions in its main [= default] section.)
[ new_oids ]
# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
# Add a simple OID like this:
# testoid1=1.2.3.4
# Or use config file substitution like this:
# testoid2=${testoid1}.5.6
# Policies used by the TSA examples.
tsa_policy1 = 1.2.3.4.1
tsa_policy2 = 1.2.3.4.5.6
tsa_policy3 = 1.2.3.4.5.7
# For FIPS
# Optionally include a file that is generated by the OpenSSL fipsinstall
# application. This file contains configuration data required by the OpenSSL
# fips provider. It contains a named section e.g. [fips_sect] which is
# referenced from the [provider_sect] below.
# Refer to the OpenSSL security policy for more information.
# .include fipsmodule.cnf
[openssl_init]
providers = provider_sect
# List of providers to load
[provider_sect]
default = default_sect
# The fips section name should match the section name inside the
# included fipsmodule.cnf.
# fips = fips_sect
# If no providers are activated explicitly, the default one is activated implicitly.
# See man 7 OSSL_PROVIDER-default for more details.
#
# If you add a section explicitly activating any other provider(s), you most
# probably need to explicitly activate the default provider, otherwise it
# becomes unavailable in openssl. As a consequence applications depending on
# OpenSSL may not work correctly which could lead to significant system
# problems including inability to remotely access the system.
[default_sect]
# activate = 1
####################################################################
[ ca ]
default_ca = CA_default # The default ca section
####################################################################
[ CA_default ]
dir = ./demoCA # Where everything is kept
certs = $dir/certs # Where the issued certs are kept
crl_dir = $dir/crl # Where the issued crl are kept
database = $dir/index.txt # database index file.
#unique_subject = no # Set to 'no' to allow creation of
# several certs with same subject.
new_certs_dir = $dir/newcerts # default place for new certs.
certificate = $dir/cacert.pem # The CA certificate
serial = $dir/serial # The current serial number
crlnumber = $dir/crlnumber # the current crl number
# must be commented out to leave a V1 CRL
crl = $dir/crl.pem # The current CRL
private_key = $dir/private/cakey.pem # The private key
x509_extensions = usr_cert # The extensions to add to the cert
# Comment out the following two lines for the "traditional"
# (and highly broken) format.
name_opt = ca_default # Subject Name options
cert_opt = ca_default # Certificate field options
# Extension copying option: use with caution.
# copy_extensions = copy
# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
# so this is commented out by default to leave a V1 CRL.
# crlnumber must also be commented out to leave a V1 CRL.
# crl_extensions = crl_ext
default_days = 365 # how long to certify for
default_crl_days= 30 # how long before next CRL
default_md = default # use public key default MD
preserve = no # keep passed DN ordering
# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy = policy_match
# For the CA policy
[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
# For the 'anything' policy
# At this point in time, you must list all acceptable 'object'
# types.
[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
####################################################################
[ req ]
default_bits = 2048
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca # The extensions to add to the self signed cert
# Passwords for private keys if not present they will be prompted for
# input_password = secret
# output_password = secret
# This sets a mask for permitted string types. There are several options.
# default: PrintableString, T61String, BMPString.
# pkix : PrintableString, BMPString (PKIX recommendation before 2004)
# utf8only: only UTF8Strings (PKIX recommendation after 2004).
# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
# MASK:XXXX a literal mask value.
# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
string_mask = utf8only
# req_extensions = v3_req # The extensions to add to a certificate request
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = AU
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Some-State
localityName = Locality Name (eg, city)
0.organizationName = Organization Name (eg, company)
0.organizationName_default = Internet Widgits Pty Ltd
# we can do this but it is not needed normally :-)
#1.organizationName = Second Organization Name (eg, company)
#1.organizationName_default = World Wide Web Pty Ltd
organizationalUnitName = Organizational Unit Name (eg, section)
#organizationalUnitName_default =
commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 64
# SET-ex3 = SET extension number 3
[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20
unstructuredName = An optional company name
[ usr_cert ]
# These extensions are added when 'ca' signs a request.
# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.
basicConstraints=CA:FALSE
# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
# subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
# subjectAltName=email:move
# Copy subject details
# issuerAltName=issuer:copy
# This is required for TSA certificates.
# extendedKeyUsage = critical,timeStamping
[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[ v3_ca ]
# Extensions for a typical CA
# PKIX recommendation.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
# Key usage: this is typical for a CA certificate. However since it will
# prevent it being used as an test self-signed certificate it is best
# left out by default.
# keyUsage = cRLSign, keyCertSign
# Include email address in subject alt name: another PKIX recommendation
# subjectAltName=email:copy
# Copy issuer details
# issuerAltName=issuer:copy
# DER hex encoding of an extension: beware experts only!
# obj=DER:02:03
# Where 'obj' is a standard or added object
# You can even override a supported extension:
# basicConstraints= critical, DER:30:03:01:01:FF
[ crl_ext ]
# CRL extensions.
# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
# issuerAltName=issuer:copy
authorityKeyIdentifier=keyid:always
[ proxy_cert_ext ]
# These extensions should be added when creating a proxy certificate
# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.
basicConstraints=CA:FALSE
# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
# subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
# subjectAltName=email:move
# Copy subject details
# issuerAltName=issuer:copy
# This really needs to be in place for it to be a proxy certificate.
proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
####################################################################
[ tsa ]
default_tsa = tsa_config1 # the default TSA section
[ tsa_config1 ]
# These are used by the TSA reply generation only.
dir = ./demoCA # TSA root directory
serial = $dir/tsaserial # The current serial number (mandatory)
crypto_device = builtin # OpenSSL engine to use for signing
signer_cert = $dir/tsacert.pem # The TSA signing certificate
# (optional)
certs = $dir/cacert.pem # Certificate chain to include in reply
# (optional)
signer_key = $dir/private/tsakey.pem # The TSA private key (optional)
signer_digest = sha256 # Signing digest to use. (Optional)
default_policy = tsa_policy1 # Policy if request did not specify it
# (optional)
other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional)
digests = sha1, sha256, sha384, sha512 # Acceptable message digests (mandatory)
accuracy = secs:1, millisecs:500, microsecs:100 # (optional)
clock_precision_digits = 0 # number of digits after dot. (optional)
ordering = yes # Is ordering defined for timestamps?
# (optional, default: no)
tsa_name = yes # Must the TSA name be included in the reply?
# (optional, default: no)
ess_cert_id_chain = no # Must the ESS cert id chain be included?
# (optional, default: no)
ess_cert_id_alg = sha256 # algorithm to compute certificate
# identifier (optional, default: sha256)
[insta] # CMP using Insta Demo CA
# Message transfer
server = pki.certificate.fi:8700
# proxy = # set this as far as needed, e.g., http://192.168.1.1:8080
# tls_use = 0
path = pkix/
# Server authentication
recipient = "/C=FI/O=Insta Demo/CN=Insta Demo CA" # or set srvcert or issuer
ignore_keyusage = 1 # quirk needed to accept Insta CA cert not including digitalsignature
unprotected_errors = 1 # quirk needed to accept negative responses possibly not protected
extracertsout = insta.extracerts.pem
# Client authentication
ref = 3078 # user identification
secret = pass:insta # can be used for both client and server side
# Generic message options
cmd = ir # default operation, can be overridden on cmd line with, e.g., kur
# Certificate enrollment
subject = "/CN=openssl-cmp-test"
newkey = insta.priv.pem
out_trusted = apps/insta.ca.crt # does not include keyUsage digitalSignature
certout = insta.cert.pem
[pbm] # Password-based protection for Insta CA
# Server and client authentication
ref = $insta::ref # 3078
secret = $insta::secret # pass:insta
[signature] # Signature-based protection for Insta CA
# Server authentication
trusted = $insta::out_trusted # apps/insta.ca.crt
# Client authentication
secret = # disable PBM
key = $insta::newkey # insta.priv.pem
cert = $insta::certout # insta.cert.pem
[ir]
cmd = ir
[cr]
cmd = cr
[kur]
# Certificate update
cmd = kur
oldcert = $insta::certout # insta.cert.pem
[rr]
# Certificate revocation
cmd = rr
oldcert = $insta::certout # insta.cert.pem
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
+68
查看文件
@@ -0,0 +1,68 @@
--------------------------------------------------------------------
The PHP License, version 3.01
Copyright (c) 1999 - 2024 The PHP Group. All rights reserved.
--------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without
modification, is permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
3. The name "PHP" must not be used to endorse or promote products
derived from this software without prior written permission. For
written permission, please contact group@php.net.
4. Products derived from this software may not be called "PHP", nor
may "PHP" appear in their name, without prior written permission
from group@php.net. You may indicate that your software works in
conjunction with PHP by saying "Foo for PHP" instead of calling
it "PHP Foo" or "phpfoo"
5. The PHP Group may publish revised and/or new versions of the
license from time to time. Each version will be given a
distinguishing version number.
Once covered code has been published under a particular version
of the license, you may always continue to use it under the terms
of that version. You may also choose to use such covered code
under the terms of any subsequent version of the license
published by the PHP Group. No one other than the PHP Group has
the right to modify the terms applicable to covered code created
under this License.
6. Redistributions of any form whatsoever must retain the following
acknowledgment:
"This product includes PHP software, freely available from
<http://www.php.net/software/>".
THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND
ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP
DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------
This software consists of voluntary contributions made by many
individuals on behalf of the PHP Group.
The PHP Group can be contacted via Email at group@php.net.
For more information on the PHP Group and the PHP project,
please see <http://www.php.net>.
PHP includes the Zend Engine, freely available at
<http://www.zend.com>.
文件差异内容过多而无法显示 加载差异
二进制文件未显示。
+1
查看文件
@@ -0,0 +1 @@
"%~dp0php.exe" "%~dp0pharcommand.phar" %*
二进制文件未显示。
文件差异内容过多而无法显示 加载差异
文件差异内容过多而无法显示 加载差异
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
文件差异内容过多而无法显示 加载差异
+153
查看文件
@@ -0,0 +1,153 @@
This snapshot was automatically generated on
Tue, 25 Aug 2026 21:28:57 +0000
Version: 8.5.10
Branch: HEAD
Build: C:\Users\runneradmin\AppData\Local\Temp\php-4b164008-6ffd-4215-abda-6879932a9885\config\vs17\x64\obj\Release
Built-in Extensions
===========================
Core
bcmath
calendar
ctype
date
filter
hash
iconv
json
SPL
pcre
random
readline
Reflection
session
standard
mysqlnd
tokenizer
zlib
libxml
dom
PDO
openssl
SimpleXML
xml
xmlreader
xmlwriter
curl
ftp
sqlite3
Phar
mbstring
mysqli
zip
Dependency information:
Module: php_curl.dll
===========================
libcrypto-3-x64.dll
libssl-3-x64.dll
libssh2.dll
nghttp2.dll
brotlidec.dll
Module: libssl-3-x64.dll
===========================
libcrypto-3-x64.dll
Module: libssh2.dll
===========================
libcrypto-3-x64.dll
Module: brotlidec.dll
===========================
brotlicommon.dll
Module: php_enchant.dll
===========================
libenchant2.dll
Module: libenchant2.dll
===========================
glib-2.dll
gobject-2.dll
gmodule-2.dll
Module: gobject-2.dll
===========================
glib-2.dll
Module: gmodule-2.dll
===========================
glib-2.dll
Module: php_ftp.dll
===========================
libcrypto-3-x64.dll
libssl-3-x64.dll
Module: php_ldap.dll
===========================
libsasl.dll
libcrypto-3-x64.dll
libssl-3-x64.dll
Module: libsasl.dll
===========================
libcrypto-3-x64.dll
Module: php_openssl.dll
===========================
libcrypto-3-x64.dll
libssl-3-x64.dll
Module: php_sodium.dll
===========================
libsodium.dll
Module: php_sqlite3.dll
===========================
libsqlite3.dll
Module: php_intl.dll
===========================
icuin77.dll
icuio77.dll
icuuc77.dll
Module: icuin77.dll
===========================
icuuc77.dll
Module: icuuc77.dll
===========================
icudt77.dll
Module: icuio77.dll
===========================
icuuc77.dll
icuin77.dll
Module: php_pdo_pgsql.dll
===========================
libpq.dll
Module: libpq.dll
===========================
libssl-3-x64.dll
libcrypto-3-x64.dll
Module: php_pdo_sqlite.dll
===========================
libsqlite3.dll
Module: php_pgsql.dll
===========================
libpq.dll
Module: php_snmp.dll
===========================
libcrypto-3-x64.dll
libssl-3-x64.dll
@@ -0,0 +1 @@
PHP is a server-side scripting language that runs on Windows via FastCGI, but the recommended Windows alternative is to use WSL2 with a Linux PHP environment for better performance and ecosystem compatibility.
+8 -243
查看文件
@@ -1,248 +1,13 @@
# ParlzPackageManger (PMM)
# PMM
> **License: GNU GPL v3** — 本项目以 GNU General Public License v3 发布,详见 `LICENSE`。
**ParlzPackageManager**cross-platform C package manager.
用 C 语言编写的跨平台包管理器(Windows / Linux / macOS),零第三方库依赖
(下载通过系统自带的 `curl` 完成,Windows 10+、macOS、主流 Linux 均预装)。
## 构建
```sh
make # Linux / macOS(或任何有 make 的环境),生成 pmm 二进制
build.bat # Windows(只需 MinGW gcc),生成 pmm.exe
make install # 安装到 /usr/local/bin(可选)
```c
#include <stdio.h>
int main(void){return 0;}
```
产物是单文件 `pmm`(或 `pmm.exe`),无 DLL/第三方库依赖。
- feature one
- feature two
> 重要:Windows 构建须用**静态链接**`-static`)。否则产物会依赖 MinGW 的
> `libmcfgthread-2.dll`,在没装 MinGW 的普通 PowerShell 里会因为找不到该 DLL 而
> **静默不输出**(加载即退出)。`Makefile`/`build.bat` 已默认加 `-static`。
## 使用
```sh
# 从镜像站(apt 式多镜像)安装
pmm install <pkg> # 按优先级从 registry 镜像查找并安装
pmm install <file.pdm> # 安装本地 .pdm 包
pmm pack <dir> [out.pdm] # 把文件夹打包成 .pdm(模拟 dpkg deb
pmm pdm install|list|remove|info ... # 管理 .pdm 包
# 从 git 托管仓库的最新 release 安装
pmm install --git https://host/owner/repo.git # 任意 git 仓库(自动探测 API 形状)
pmm install --github owner/repo # GitHub
pmm install --gitlab owner/repo # GitLab
pmm install --gitea https://git.example.com/owner/repo # Gitea/Forgejo(含私人部署)
pmm install --git owner/repo --host forgejo # 强制指定主机类型
pmm mirror list / add <名> <源> / use <名> / remove <名>
pmm self-update # 从镜像安装最新版 pmm(自动识别本机 os/arch
pmm list
pmm help / version
pmm install <pkg> -pd # 安装到 D:\.pmm(可换成 -pc 回到 C 盘,-px 任意盘)
```
## 换安装盘(-p<drive>,避免塞爆 C 盘)
**默认安装在 `D:\.pmm`**config + cache + bin + root 全在这里,避免占满 C 盘)。
`-p<盘符>` 可以改变 PMM 的**整个家目录**:
- 默认(不带 `-p`)→ `D:\.pmm\`node 等 .exe 装到 `D:\.pmm\bin``.pdm` 装到 `D:\.pmm\root`
- `pmm install xxx -pc` → 回到用户目录 `C:\Users\<你>\.pmm`
- `pmm install xxx -pe` → 任意其它盘(`E:\.pmm\`);也支持 `-p D`(空格分隔)写法
**记忆规则**:第一次用某个 `-p<盘>` 会被记住(写入 `~/.pmm/install-drive`),
之后的命令不写也会自动用那个盘;想换盘用新的 `-p<盘>` 即可。
> 注意:换盘会把配置/镜像源也挪到对应盘的 `.pmm`(如 `D:\.pmm\mirror.ini`)。
> 切换后如要装包,先在该盘目录放好镜像配置,或重新 `pmm mirror add ...`。
`--git` 对任意 git 仓库地址自动探测 API 形状(Gitea/Forgejo → GitLab → GitHub),
GitHub.com、GitLab.com、Gitea、Forgejo 及一切私有部署实例都支持。
## 下载校验(SHA-256 / SHA-1
每次下载完成后自动计算并打印 SHA-256 与 SHA-1;若 release 资源旁有
`<资源名>.sha256``<资源名>.sha1` 伴随文件,会自动核对,不一致时拒绝安装
并删除缓存文件。registry 条目里若带 `sha256` 字段,也会作为独立校验来源。
SHA 实现为纯 C 内置(FIPS 180-4),无外部依赖。
## .pdm 包格式(模拟 dpkg 的 deb
`pmm pack ./目录` 会把目录打包成一个 `.pdm`(一个 tar 归档,含
`control.tar.gz` / `data.tar.gz` / `sha256sums` 三个成员,与 deb 的
`control.tar`+`data.tar` 思路一致):
- 目录里需有一个 `pdm-control` 文件,格式为 `KEY: value` 行:
`Package` / `Version` / `Architecture` / `Description` / `Maintainer`
- 其余文件全部作为文件有效载荷,安装时解包到安装根目录(`~/.pmm/root`
- `pmm install <pkg.pdm>` 安装,`pmm remove` 卸载
- 安装记录写入 `~/.pmm/installed/<包名>.info`,含文件清单,用于卸载
## apt 式镜像(mirror.ini / mirror.conf
镜像配置文件位于 `~/.pmm/`Windows 为 `%USERPROFILE%\.pmm\`),查找顺序
`mirror.ini``mirror.conf`。每个 `[名]` 是一个镜像,字段如下:
```ini
# ~/.pmm/mirror.ini
[sz]
registry = https://sz.pmm.parlz.com/mirror/packages
priority = 1
default = true
[main]
registry = https://pmm.parlz.com/mirror/packages
priority = 20
# 其它字段(可选):
download = https://ghfast.example-mirror.dev # 下载前缀(gh-proxy 风格,接到原始 URL 前)
api = https://api.mirror.example # git 主机 API 基地址(整体替换)
```
- `pmm install <包>` 会**按 priority 从小到大依次尝试**每个镜像的
`{registry}/{包}.json`,第一个成功的镜像命中,否则回退到下一个(apt 的逻辑)。
- 下载文件时也会把镜像的 `download` 前缀按优先级排在原始 URL 之前逐个尝试。
- 每家的 `priority` 相同则按出现顺序;`pmm mirror use <名>` 可强制优先用某个镜像。
## 镜像仓库(mirror/ 静态目录)
`mirror/` 是一个**分级目录浏览器 + 静态包仓库**:`index.php` 一页只显示当前目录内容(文件夹/文件,可点进下级、可下载),当前目录有 README.md 会自动解析显示,背景纯黑/纯白(`?bg=white`)。
```sh
cd mirror
php -S 0.0.0.0:8080 # 浏览页 + 静态下载(PHP,无 router 参数)
# 或 nginx/apacheDirectoryIndex index.php,.pdm 走静态文件
```
- 目录约定:`packages/<包>.json`latest,含 `variants`)、
`packages/<包>/<版本>-<平台>.pdm`(包文件)、`packages/<包>/<版本>-<平台>.json`(元数据)。
- 客户端把 `~/.pmm/mirror.ini` 指到该目录的 `packages`
```ini
[main]
registry = http://<host>:8080/packages # 生产 https://pmm.parlz.com/mirror/packages
priority = 1
```
然后 `pmm install nodejs`(自动识平台)即可从这个静态仓库下载安装。
- `mirror.ini` 也可从此目录按需生成(`registry` 指向 `/packages`)。
`pmm install <包>` 装 latest,也支持 **Python/pip 风格的版本限定**
```sh
pmm install nodejs # latest
pmm install nodejs==24.20.0 # 精确版本
pmm install nodejs>=24,<25 # 版本区间(选出范围内最高)
pmm install nodejs<=24.20.0 # <= / >= / < / > / != , 逗号组合
pmm install "nodejs>=24,<25" # 带空格/组合时加引号
```
## Release 资源与平台对应
| 系统 | 优先匹配的扩展名 |
|---|---|
| windows | `.exe` `.msi` `.zip` `.7z` |
| linux | `.deb` `.rpm` `.appimage` `.tar.gz` `.tgz` `.tar.xz` `.tar.bz2` `.pkg.tar.zst` |
| macos | `.dmg` `.pkg` `.app.zip` `.tar.gz` `.tgz` |
校验文件 / 源码包(`.sha256`、`checksum*`、`-src` 等)会被自动跳过。
## 主机支持
- **GitHub**`https://api.github.com/repos/{repo}/releases/latest`
- **GitLab**`https://gitlab.com/projects/{repo}/releases/permalink/latest`
- **Gitea / Forgejo**(含私人部署):`{origin}/api/v1/repos/{repo}/releases/latest`
镜像源会整体替换 API 基地址,加速代理和私有实例都走同一条路。
## 安装行为(下载后自动安装)
下载完成并通过校验后,pmm 会自动执行对应平台的安装动作,无需再手动操作:
- `.deb` → `sudo dpkg -i`;`.rpm` → `sudo rpm -Uvh`Linux
- `.msi` → `msiexec /i /qn`Windows,静默安装)
- `.exe` → 自动运行安装器(Windows,带常见静默旗标 `/S /VERYSILENT /quiet --silent`
- `.dmg` → `hdiutil attach` + `sudo cp` 复制 `.app` 到 `/Applications`;`.pkg` → `sudo installer`macOS
- `.tar.*` / `.zip` / `.7z` → 解压到安装目录(`~/.pmm/bin`
- `.pdm` → 通过内置 .pdm 管理器解包到 `~/.pmm/root`;`.appimage` → 加执行权限后放入安装目录
> Windows 下 pmm 通过系统 `cmd.exe` 执行命令,因此 `.exe` 用 cmd 兼容的单条静默命令。
## 已安装软件登记到注册表(Installed Apps
安装 `.pdm` 包后,pmm 会模仿 **MSI/NSIS 安装器** 的做法,把软件写入 **每用户
注册表卸载项** `HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\<包名>`,
于是它会出现在 Windows「应用 / Installed Apps」里,带:
- `DisplayName` / `DisplayVersion` / `Publisher`(取自控制信息)
- `DisplayIcon`(包内找到的第一个 `.exe`
- `UninstallString` → `"<pmm.exe>" remove <包名>`,让「卸载」按钮真正调用我们
- `InstallLocation`、`EstimatedSize`、`InstallDate`、`NoModify`、`NoRepair`
`pmm remove <包>`(或控制面板"卸载"按钮)会删除文件并清理该注册表项。
(每用户 HKCU 写入,无需管理员权限,重启终端/刷新设置可见。)
## 自动加入 PATH
每次成功安装后,pmm 会把 PMM 目录 **自动加入用户 PATH**(幂等,不重复):
- 始终加入 `~/.pmm/bin` 与 `~/.pmm/root`;
- 还会扫描 `~/.pmm/root/` 下含有可执行文件的子目录并加入,例如安装 Node.js 后
会自动加入 `~/.pmm/root/nodejs`;对 `pkg/cmd` 布局(如 Git/MinGit 的
`git/cmd/git.exe`)会识别到第二层并加入 `~/.pmm/root/git/cmd`。
- Windows 通过用户注册表 PATH 持久化(`setx`),新开终端即生效;Linux/macOS
写 `~/.bashrc`(重新加载 shell 生效)。
## 现成的 .pdm 包(dist/
- `nodejs-24.20.0.pdm` — Node.js 24.20.0Windows,源自官方 MSI
- `nodejs-linux-24.20.0.pdm` — Node.js 24.20.0Linux x64,源自官方 .tar.xz
- `git-2.55.0.5.pdm` — Git for Windows 2.55.0.5MinGit 官方发行版)
- `pureftpd-1.0.51.pdm` — **Pure-FTPd 1.0.51Linux**:源码分发,装到 Linux 的 `~/.pmm/root`
后需 `./configure && make && sudo make install` 编译使用(官方只发布源码)。
## 自动识别平台
镜像站把同一软件做成**多平台变体**(`variants` 数组,每项带 `os`)。客户端安装时
**自动按当前系统选平台**
```sh
pmm install nodejs # Windows 上装 node.exe 版,Linux 上自动装 ELF 版
pmm install nodejs
```
- Windows 机 `install nodejs` → 拉取 `os=windows` 变体(`packages/nodejs/24.20.0-windows.pdm`
- Linux 机 `install nodejs` → 拉取 `os=linux` 变体(`packages/nodejs/24.20.0-linux.pdm`
- 版本语义仍支持 `==` / `>=` / `<=` / `,` 组合,且在**匹配当前平台**的版本里选最高。
## 跨环境可靠性
`.pdm`/`.deb` 等 tar 相关安装动作先切换到 PMM 目录再用**相对路径**执行,避免
git-bash 的 MSYS GNU tar(认 `/c/...`)与 Windows 自带的 bsdtar(认 `C:\...`
对盘符路径解析不一致的问题。
## registry 格式(pmm install xxx
每个镜像的 `{registry}/{包名}.json` 描述一个包;`url` + `file` 指向发布文件,
`sha256`(可选)作为该文件的独立校验来源:
```json
{ "version": "1.2.3", "url": "https://.../foo-1.2.3-win64.exe",
"file": "foo-1.2.3-win64.exe", "sha256": "..." }
```
## 目录结构
```
src/main.c 命令入口(pmm
src/json.c/.h 内置 JSON 解析器
src/ini.c/.h INI/.conf 解析器
src/http.c/.h 系统 curl 封装
src/repo.c/.h 仓库适配(GitHub/GitLab/Gitea/Forgejo + 通用 URL 自动探测)
src/sha256.c/.h SHA-256FIPS 180-4
src/sha1.c/.h SHA-1FIPS 180-4
src/mirrors.c/.h apt 式镜像列表(priority 排序 + 下载回退)
src/pdm.c/.h .pdm 打包/安装/卸载(模拟 deb)
src/install.c/.h 下载、校验与各平台安装
mirror/ 镜像站(Node.js 网页版)
examples/ 配置文件示例
```
[GitHub](https://github.com/JGZYES/ParlzPackageManger)
+7
查看文件
@@ -0,0 +1,7 @@
# PMM 镜像源配置 (与 mirror.ini 格式完全相同)
[ghfast]
api = https://ghfast.example-mirror.dev
default = true
[mygitea]
api = https://git.mycompany.local
+11
查看文件
@@ -0,0 +1,11 @@
# PMM 镜像源配置 (mirror.conf 与本文件格式相同)
# apt 式:priority 越小越优先;default=true 为未显式指定时的默认镜像。
#
[sz]
registry = https://sz.pmm.parlz.com/mirror/packages
priority = 1
default = true
[main]
registry = https://pmm.parlz.com/mirror/packages
priority = 20
+5
查看文件
@@ -0,0 +1,5 @@
Package: hello
Version: 1.0.0
Architecture: all
Description: A demo .pdm package
Maintainer: PMM example <example@pmm.dev>
+3
查看文件
@@ -0,0 +1,3 @@
# PMM 配置 (与 pmm.json / pmm.ini 等价)
registry = https://packages.example.com/pmm
mirror = ghfast
+3
查看文件
@@ -0,0 +1,3 @@
# PMM 配置 (与 pmm.json 等价,二选一,查找顺序: pmm.json > pmm.ini > pmm.conf)
registry = https://packages.example.com/pmm
mirror = ghfast
+4
查看文件
@@ -0,0 +1,4 @@
{
"registry": "https://packages.example.com/pmm",
"mirror": "ghfast"
}
二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 3.5 KiB

+13
查看文件
@@ -0,0 +1,13 @@
/* http.h - HTTP(S) via system curl (curl is preinstalled on Win10+/macOS/most Linux) */
#ifndef PMM_HTTP_H
#define PMM_HTTP_H
#include <stddef.h>
/* GET a URL, return malloc'd body or NULL on failure. Sets *status (may be NULL). */
char *http_get(const char *url, int *status);
/* Download URL to file path. Returns 0 on success. */
int http_download(const char *url, const char *out_path);
#endif
+81
查看文件
@@ -0,0 +1,81 @@
/* ini.c - tiny INI / .conf parser */
#include "ini.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
static char *trim(char *s) {
while (*s && isspace((unsigned char)*s)) s++;
char *end = s + strlen(s);
while (end > s && isspace((unsigned char)end[-1])) *--end = '\0';
return s;
}
Ini *ini_load(const char *path) {
FILE *f = fopen(path, "r");
if (!f) return NULL;
Ini *ini = calloc(1, sizeof(Ini));
if (!ini) { fclose(f); return NULL; }
char line[4096];
char section[512] = "";
IniEntry *tail = NULL;
while (fgets(line, sizeof(line), f)) {
char *s = trim(line);
if (*s == '\0' || *s == '#' || *s == ';') continue;
if (*s == '[') {
char *close = strchr(s, ']');
if (close) {
*close = '\0';
strncpy(section, s + 1, sizeof(section) - 1);
section[sizeof(section) - 1] = '\0';
char *t = trim(section);
memmove(section, t, strlen(t) + 1);
}
continue;
}
char *eq = strchr(s, '=');
if (!eq) eq = strchr(s, ':');
if (!eq) continue;
*eq = '\0';
char *key = trim(s);
char *value = trim(eq + 1);
IniEntry *e = calloc(1, sizeof(IniEntry));
e->section = strdup(*section ? section : "");
e->key = strdup(key);
e->value = strdup(value);
if (tail) tail->next = e; else ini->head = e;
tail = e;
}
fclose(f);
return ini;
}
void ini_free(Ini *ini) {
if (!ini) return;
IniEntry *e = ini->head;
while (e) {
IniEntry *n = e->next;
free(e->section); free(e->key); free(e->value); free(e);
e = n;
}
free(ini);
}
const char *ini_get(const Ini *ini, const char *section, const char *key) {
if (!ini) return NULL;
if (!section) section = "";
for (IniEntry *e = ini->head; e; e = e->next)
if (strcmp(e->section, section) == 0 && strcmp(e->key, key) == 0)
return e->value;
return NULL;
}
const char *ini_find_key_by_value(const Ini *ini, const char *section, const char *value) {
if (!ini) return NULL;
if (!section) section = "";
for (IniEntry *e = ini->head; e; e = e->next)
if (strcmp(e->section, section) == 0 && strcmp(e->value, value) == 0)
return e->key;
return NULL;
}
+28
查看文件
@@ -0,0 +1,28 @@
/* ini.h - tiny INI / .conf parser for PMM.
* Format:
* [section]
* key = value (# or ; comment)
* Also accepts key = value lines without a section (section = "").
*/
#ifndef PMM_INI_H
#define PMM_INI_H
typedef struct IniEntry {
char *section;
char *key;
char *value;
struct IniEntry *next;
} IniEntry;
typedef struct {
IniEntry *head;
} Ini;
Ini *ini_load(const char *path);
void ini_free(Ini *ini);
/* Lookup value; section may be NULL or "" for top-level. Returns NULL if absent. */
const char *ini_get(const Ini *ini, const char *section, const char *key);
/* First key in given section whose value matches; returns key name or NULL. */
const char *ini_find_key_by_value(const Ini *ini, const char *section, const char *value);
#endif
+565
查看文件
@@ -0,0 +1,565 @@
/* install.c - download + per-OS install logic
*
* Install methods:
* windows: .exe -> run installer silently where known, else copy to bin
* .msi -> msiexec /i /qn
* .zip/.7z -> extract into install dir
* linux: .deb -> dpkg -i .rpm -> rpm -Uvh
* .appimage -> chmod +x, copy to bin
* .tar.gz/.tgz/.tar.xz -> extract into install dir
* macos: .dmg -> hdiutil attach, copy .app to /Applications, detach
* .pkg -> installer -pkg
* .tar.gz/.tgz -> extract into install dir
*/
#include "install.h"
#include "pmm.h"
#include "http.h"
#include "json.h"
#include "sha256.h"
#include "sha1.h"
#include "mirrors.h"
#include "pdm.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <direct.h>
#define PMM_MKDIR(p) _mkdir(p)
#else
#include <sys/stat.h>
#define PMM_MKDIR(p) mkdir((p), 0755)
#endif
static int has_suffix(const char *s, const char *suf) {
size_t sl = strlen(s), xl = strlen(suf);
return sl >= xl && strcasecmp(s + sl - xl, suf) == 0;
}
static const char *base_name(const char *path) {
const char *a = strrchr(path, '/');
const char *b = strrchr(path, '\\');
const char *p = (a > b) ? a : b;
return p ? p + 1 : path;
}
static int run_cmd_quiet(const char *cmd) {
int rc = system(cmd);
return rc == 0 ? 0 : -1;
}
/* Strip "<algo> *path" style prefix from checksum files; keep last token's hex. */
static void extract_hex(const char *text, const char *filename, char *hex, size_t hexsize) {
hex[0] = '\0';
const char *line = text;
while (line && *line) {
const char *eol = strchr(line, '\n');
size_t ll = eol ? (size_t)(eol - line) : strlen(line);
char buf[1024];
if (ll >= sizeof(buf)) ll = sizeof(buf) - 1;
memcpy(buf, line, ll);
buf[ll] = '\0';
/* trim */
char *s = buf;
while (*s == ' ' || *s == '\t' || *s == '\r') s++;
size_t sl = strlen(s);
while (sl && (s[sl-1] == ' ' || s[sl-1] == '\t' || s[sl-1] == '\r')) s[--sl] = '\0';
/* does this line mention the file (or is a bare hash)? */
char *hend = s;
while (*hend && *hend != ' ' && *hend != '\t' && *hend != '*') hend++;
int is_hex_prefix = 1;
for (char *p = s; p < hend; p++)
if (!((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')))
{ is_hex_prefix = 0; break; }
if (is_hex_prefix && hend > s) {
/* bare "<hex>" or "<hex> *file" */
if (!*hend || strstr(hend, filename)) {
snprintf(hex, hexsize, "%.*s", (int)(hend - s), s);
return;
}
} else if (strstr(s, filename)) {
/* "hash file" reversed order unlikely; take first token */
char tok[256]; size_t t = 0;
while (s[t] && s[t] != ' ' && s[t] != '\t' && t < sizeof(tok) - 1) { tok[t] = s[t]; t++; }
tok[t] = '\0';
int ok = *tok;
for (char *p = tok; *p; p++)
if (!((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')))
{ ok = 0; break; }
if (ok) { snprintf(hex, hexsize, "%s", tok); return; }
}
line = eol ? eol + 1 : NULL;
}
}
/* Best-effort sha256/sha1 verification against sidecar checksum files
* (<url>.sha256 / <url>.sha1). Returns 0 on ok-or-no-checksum, -1 on mismatch. */
static int verify_checksums(const char *url, const char *path, const char *name) {
char hex[128];
struct { const char *ext; int algo; } algos[] = {
{ ".sha256", 256 }, { ".sha1", 1 }, { NULL, 0 }
};
for (int i = 0; algos[i].ext; i++) {
char csum_url[2400];
snprintf(csum_url, sizeof(csum_url), "%s%s", url, algos[i].ext);
int status = 0;
char *text = http_get(csum_url, &status);
if (!text || (status != 200 && status != 0)) { free(text); continue; }
if (algos[i].algo == 256) {
if (pmm_sha256_file(path, hex) != 0) { free(text); return -1; }
} else {
if (pmm_sha1_file(path, hex) != 0) { free(text); return -1; }
}
char expect[128];
extract_hex(text, name, expect, sizeof(expect));
free(text);
if (!expect[0]) {
printf("pmm: warning: could not parse %s checksum file\n", algos[i].ext);
continue;
}
/* case-insensitive compare */
if (strcasecmp(expect, hex) != 0) {
fprintf(stderr, "pmm: CHECKSUM MISMATCH (%s)!\n expected: %s\n actual: %s\n",
algos[i].ext, expect, hex);
return -1;
}
printf("pmm: %s checksum OK: %s\n", algos[i].algo == 256 ? "sha256" : "sha1", hex);
}
return 0;
}
/* A bare (no-dot) release asset on Unix must actually be usable on this
* platform — e.g. fzf publishes a bare 'fzf' for Android, which must not be
* installed on Linux. Uses `file`; be permissive where it isn't available. */
static int native_binary_ok(const char *path, PmmOS os) {
#ifdef _WIN32
(void)path; (void)os; return 1; /* `file` isn't present on Windows */
#else
if (os != OS_LINUX && os != OS_MACOS) return 1;
char cmd[1400], out[512] = "";
snprintf(cmd, sizeof(cmd), "file -b \"%s\" 2>/dev/null", path);
FILE *f = popen(cmd, "r");
if (!f) return 1;
if (fgets(out, sizeof(out), f) == NULL) out[0] = 0;
pclose(f);
if (!out[0]) return 1; /* `file` missing: allow */
if (strstr(out, "Android") || strstr(out, "for Android")) return 0; /* android ELF */
if (strstr(out, "PE32")) return 0; /* Windows binary */
if (strstr(out, "Mach-O")) return 0; /* macOS binary */
return 1;
#endif
}
static int install_path(const char *path, const char *name);
int pmm_no_cache = 0; /* --no-cache: drop cached file before download */
int install_file(const char *url, const char *name) {
char cache[1024], path[1200];
pmm_cache_dir(cache, sizeof(cache));
snprintf(path, sizeof(path), "%s/%s", cache, name);
if (pmm_no_cache) remove(path); /* force a fresh download */
/* apt-style fallback: mirrors (by priority) first, then the origin URL */
MirrorList *ml = mirrors_load();
int ncand = 0;
char **cands = mirrors_download_candidates(ml, url, &ncand);
int ok = -1;
for (int i = 0; i < ncand; i++) {
printf("pmm: downloading %s%s\n", cands[i],
i < ncand - 1 ? " (mirror)" : "");
if (http_download(cands[i], path) == 0) { ok = 0; break; }
fprintf(stderr, "pmm: download failed, trying next source...\n");
remove(path);
}
for (int i = 0; i < ncand; i++) free(cands[i]);
free(cands);
mirrors_free(ml);
if (ok != 0) {
fprintf(stderr, "pmm: all download sources failed: %s\n", url);
return -1;
}
printf("pmm: downloaded %s\n", path);
/* integrity: sha256 + sha1, verified against sidecar checksums when present */
char hex[128];
if (pmm_sha256_file(path, hex) == 0)
printf("pmm: sha256: %s\n", hex);
if (pmm_sha1_file(path, hex) == 0)
printf("pmm: sha1: %s\n", hex);
if (verify_checksums(url, path, name) != 0) {
fprintf(stderr, "pmm: refusing to install: checksum verification failed\n");
remove(path);
return -1;
}
return install_path(path, name);
}
/* Install an already-local file path by its extension. Shared by downloads
* (install_file) and local `pmm install -dpkg/-msi <file>` / `install file.deb`.
* Returns 0 on success. */
static int install_path(const char *path, const char *name) {
PmmOS os = pmm_detect_os();
char dest[1024];
pmm_install_dir(dest, sizeof(dest));
const char *bname = base_name(name);
/* A bare binary (no extension) must really be a native executable for this
* OS; otherwise refuse so the caller can fall back to the os-named asset. */
if (strchr(bname, '.') == NULL) {
printf("pmm: verifying %s is a native %s binary...\n", bname, pmm_os_name(os));
if (!native_binary_ok(path, os)) {
fprintf(stderr, "pmm: %s is not a usable %s binary; will fall back\n",
bname, pmm_os_name(os));
return -1;
}
}
/* forward-slash clone of the path (native exes/tools accept it and the
* git-bash shell won't mangle a command word like "C:/...") */
char pwin[1200];
snprintf(pwin, sizeof(pwin), "%s", path);
for (char *q = pwin; *q; q++) if (*q == '\\') *q = '/';
char cmd[2600];
/* .pdm packages are installed through the deb-like manager */
if (has_suffix(bname, ".pdm") || has_suffix(bname, ".PDM")) {
extern int pdm_install_file(const char *path);
printf("pmm: installing .pdm %s ...\n", bname);
if (pdm_install_file(path) != 0) {
fprintf(stderr, "pmm: .pdm install failed: %s\n", bname);
return -1;
}
printf("pmm: installed .pdm %s\n", bname);
pmm_add_to_path();
return 0;
}
if (has_suffix(bname, ".deb") && os == OS_LINUX) {
snprintf(cmd, sizeof(cmd), "sudo dpkg -i \"%s\"", path);
} else if (has_suffix(bname, ".rpm") && os == OS_LINUX) {
snprintf(cmd, sizeof(cmd), "sudo rpm -Uvh \"%s\"", path);
} else if (has_suffix(bname, ".appimage") && os == OS_LINUX) {
snprintf(cmd, sizeof(cmd), "chmod +x \"%s\" && mv -f \"%s\" \"%s/\"", path, path, dest);
} else if ((has_suffix(bname, ".tar.gz") || has_suffix(bname, ".tgz") ||
has_suffix(bname, ".tar.xz") || has_suffix(bname, ".tar.bz2") ||
has_suffix(bname, ".zip") || has_suffix(bname, ".7z"))) {
if (has_suffix(bname, ".zip"))
snprintf(cmd, sizeof(cmd), "unzip -o \"%s\" -d \"%s\"", path, dest);
else if (has_suffix(bname, ".7z"))
snprintf(cmd, sizeof(cmd), "7z x -y -o\"%s\" \"%s\"", dest, path);
else
snprintf(cmd, sizeof(cmd), "tar -xf \"%s\" -C \"%s\"", path, dest);
} else if (has_suffix(bname, ".dmg") && os == OS_MACOS) {
snprintf(cmd, sizeof(cmd),
"h=$(hdiutil attach \"%s\" | grep -o '/Volumes/.*' | head -1); "
"sudo cp -R \"$h\"/*.app /Applications/ 2>/dev/null; hdiutil detach \"$h\"",
path);
} else if (has_suffix(bname, ".pkg") && os == OS_MACOS) {
snprintf(cmd, sizeof(cmd), "sudo installer -pkg \"%s\" -target /", path);
} else if (has_suffix(bname, ".msi") && os == OS_WINDOWS) {
/* silent install; note system() runs cmd.exe on Windows, so use single
* slashes and a forward-slash path (cmd accepts both) */
snprintf(cmd, sizeof(cmd), "msiexec /i \"%s\" /qn", pwin);
} else if (has_suffix(bname, ".exe") && os == OS_WINDOWS) {
/* auto-run the installer. system() runs cmd.exe, which chokes on shell
* control operators (|| / &) here, so issue ONE silent invocation
* (common silent flags; vendors ignore the ones they don't know). */
snprintf(cmd, sizeof(cmd),
"\"%s\" /S /VERYSILENT /quiet --silent 2>nul", pwin);
} else {
/* unknown type for this OS: copy the single file into the install dir;
* on Linux/macOS make it executable (bare ELF/script releases). */
if (os == OS_LINUX || os == OS_MACOS)
snprintf(cmd, sizeof(cmd),
"cp -f \"%s\" \"%s/\" 2>/dev/null && chmod +x \"%s/%s\" 2>/dev/null || true",
path, dest, dest, bname);
else
snprintf(cmd, sizeof(cmd), "cp -f \"%s\" \"%s/\" 2>/dev/null || true", path, dest);
}
printf("pmm: installing %s ...\n", bname);
if (run_cmd_quiet(cmd) != 0) {
fprintf(stderr, "pmm: install command failed: %s\n", cmd);
return -1;
}
printf("pmm: installed %s\n", bname);
pmm_add_to_path();
return 0;
}
/* Install a local file by its extension — e.g. `pmm install -dpkg foo.deb`
* (Linux) or `pmm install -msi foo.msi` (Windows). Returns 0 on success. */
int install_local_file(const char *path) {
if (!path || !*path) { fprintf(stderr, "pmm: empty file path\n"); return -1; }
FILE *chk = fopen(path, "rb");
if (!chk) { fprintf(stderr, "pmm: cannot open file: %s\n", path); return -1; }
fclose(chk);
printf("pmm: installing local file %s\n", path);
return install_path(path, path);
}
/* ---------- python-style version selection (==,>=,<=,>,<,!= , comma list) ---------- */
/* Compare dotted version strings numerically ("24.20.0" vs "24.2.0"). */
static int vcmp(const char *a, const char *b) {
const char *pa = a, *pb = b;
while (*pa || *pb) {
double va = 0, vb = 0; int ha = 0, hb = 0;
while (*pa && *pa != '.' && *pa != '-') { va = va * 10 + (*pa - '0'); pa++; ha = 1; }
while (*pb && *pb != '.' && *pb != '-') { vb = vb * 10 + (*pb - '0'); pb++; hb = 1; }
if (ha != hb) return ha < hb ? -1 : 1;
if (va != vb) return va < vb ? -1 : 1;
if (*pa == '.') pa++; if (*pb == '.') pb++;
}
return 0;
}
/* Does `ver` satisfy one `cond` like ">=24.20.0"? (bare "24.20.0" means ==) */
static int cond_match(const char *ver, const char *cond) {
while (*cond == ' ' || *cond == '\t') cond++;
char op[3] = {0}; int oi = 0;
if (cond[0] == '=' && cond[1] == '=') { op[0] = '='; op[1] = '='; oi = 2; }
else if (cond[0] == '!' && cond[1] == '=') { op[0] = '!'; op[1] = '='; oi = 2; }
else if (cond[0] == '>' && cond[1] == '=') { op[0] = '>'; op[1] = '='; oi = 2; }
else if (cond[0] == '<' && cond[1] == '=') { op[0] = '<'; op[1] = '='; oi = 2; }
else if (cond[0] == '>') { op[0] = '>'; oi = 1; }
else if (cond[0] == '<') { op[0] = '<'; oi = 1; }
else { op[0] = '='; op[1] = '='; oi = 2; } /* bare version == */
const char *num = cond + oi;
while (*num == ' ' || *num == '\t') num++;
int c = vcmp(ver, num);
if (oi == 2 && op[0] == '=' && op[1] == '=') return c == 0;
if (oi == 2 && op[0] == '!' && op[1] == '=') return c != 0;
if (oi == 2 && op[0] == '>' && op[1] == '=') return c >= 0;
if (oi == 2 && op[0] == '<' && op[1] == '=') return c <= 0;
if (op[0] == '>') return c > 0;
if (op[0] == '<') return c < 0;
return 0;
}
/* ver satisfies a comma-separated spec like ">=24,<25" */
static int spec_match(const char *ver, const char *spec) {
char tmp[256];
strncpy(tmp, spec, 255);
tmp[255] = '\0';
char *p = tmp;
while (*p) {
while (*p == ' ' || *p == '\t' || *p == ',') p++;
if (!*p) break;
char *start = p;
while (*p && *p != ',') p++;
char hold = *p;
*p = '\0';
if (!cond_match(ver, start)) return 0;
if (hold) *p = hold;
}
return 1;
}
/* Pick the HIGHEST variant matching current OS + version spec from a `variants`
* array; returns the selected variant JsonValue* or NULL. */
static const char *cur_os_name(void) {
const char *ov = getenv("PMM_OS_OVERRIDE"); /* debug/testing */
if (ov && *ov) return ov;
switch (pmm_detect_os()) { case OS_WINDOWS: return "windows"; case OS_LINUX: return "linux";
case OS_MACOS: return "macos"; default: return "any"; }
}
static const char *cur_arch_name(void) {
const char *av = getenv("PMM_ARCH_OVERRIDE"); /* debug/testing */
if (av && *av) return av;
return pmm_detect_arch();
}
/* ---------- dependency resolution (Depends: "name (op ver), ...") ---------- */
static const char *dep_seen[128];
static int dep_seen_n = 0;
static int install_dep_spec(const char *depstr); /* fwd (mutually recursive) */
static int parse_dep(const char *d, char *name, size_t ns, char *spec, size_t ss) {
const char *paren = strchr(d, '(');
if (paren) {
size_t nl = (size_t)(paren - d);
while (nl && (d[nl-1]==' '||d[nl-1]=='\t')) nl--;
if (nl >= ns) nl = ns-1;
memcpy(name, d, nl); name[nl] = 0;
const char *c = paren + 1, *ce = strchr(c, ')');
if (!ce) ce = c + strlen(c);
size_t sl = (size_t)(ce - c); if (sl >= ss) sl = ss-1;
memcpy(spec, c, sl); spec[sl] = 0;
} else {
size_t nl = strlen(d);
while (nl && (d[nl-1]==' '||d[nl-1]=='\t')) nl--;
if (nl >= ns) nl = ns-1;
memcpy(name, d, nl); name[nl] = 0; spec[0] = 0;
}
char *p = name; while (*p==' '||*p=='\t') p++;
if (p != name) memmove(name, p, strlen(p)+1);
int se = (int)strlen(spec); while (se && (spec[se-1]==' '||spec[se-1]=='\t')) spec[--se]=0;
return name[0] ? 0 : -1;
}
/* Install a comma-separated Depends list. Exposed so local .pdm installs resolve deps. */
int pmm_install_dep_list(const char *list) {
if (!list || !*list) return 0;
char *dup = strdup(list), *p = dup;
while (p && *p) {
char *end = strchr(p, ',');
if (end) *end = 0;
char *t = p; while (*t==' '||*t=='\t') t++;
if (*t) install_dep_spec(t);
if (!end) break;
p = end + 1;
}
free(dup);
return 0;
}
int install_from_registry(const char *name, const char *spec, const char *mirror_active) {
if (!name || !*name) return -1;
MirrorList *ml = mirrors_load();
int has_reg = 0;
for (int i = 0; i < ml->count; i++)
if (ml->items[i].registry && *ml->items[i].registry) has_reg = 1;
if (!has_reg) {
fprintf(stderr,
"pmm: no registry mirror configured. Add to ~/.pmm/mirror.ini:\n"
" [name]\n registry = https://host/pmm\n"
" (apt-style: mirrors are tried by priority; the first with the\n"
" package wins, else plain 'pmm install --git owner/repo')\n");
mirrors_free(ml);
return -1;
}
/* Build registry base order: active mirror first, then by priority, deduped. */
char *bases[128];
int nb = 0;
int seen[128] = {0};
if (mirror_active && *mirror_active) {
for (int i = 0; i < ml->count; i++)
if (ml->items[i].registry && *ml->items[i].registry &&
strcmp(ml->items[i].name, mirror_active) == 0)
{ bases[nb++] = ml->items[i].registry; seen[i] = 1; break; }
}
for (int i = 0; i < ml->count && nb < 128; i++)
if (!seen[i] && ml->items[i].registry && *ml->items[i].registry)
bases[nb++] = ml->items[i].registry;
if (nb == 0) { fprintf(stderr, "pmm: no registry mirrors\n"); mirrors_free(ml); return -1; }
/* fetch the <pkg>.json latest pointer (carries the variants list) */
char url[2048];
int status = 0;
char *body = NULL;
char *used_base = NULL;
for (int i = 0; i < nb; i++) {
snprintf(url, sizeof(url), "%s/%s.json", bases[i], name);
printf("pmm: looking up %s in mirror %s\n", name, bases[i]);
body = http_get(url, &status);
if (getenv("PMM_DEBUG")) fprintf(stderr, "[reg] %s -> body=%s status=%d\n",
bases[i], body ? "set" : "NULL", status);
/* accept any body that isn't an explicit not-found/server error; some
* curl builds don't emit the status marker the way we expect, so a
* non-NULL body with an indeterminate status should still be parsed */
if (body && status != 404 && status != 403 && status != 503) { used_base = bases[i]; break; }
free(body); body = NULL;
}
if (!body) {
fprintf(stderr, "pmm: package '%s' not found in any registry mirror\n", name);
mirrors_free(ml);
return -1;
}
JsonValue *meta = json_parse(body);
free(body);
if (!meta || meta->type != JSON_OBJECT) {
fprintf(stderr, "pmm: bad registry entry for '%s' (%s)\n", name, used_base);
json_free(meta);
mirrors_free(ml);
return -1;
}
/* resolve declared dependencies before installing this package */
JsonValue *deps = json_get(meta, "depends");
if (deps && deps->type == JSON_ARRAY) {
for (int i = 0; i < deps->count; i++) {
JsonValue *dv = json_at(deps, i);
if (dv && dv->type == JSON_STRING) install_dep_spec(dv->string);
}
}
const char *osn = cur_os_name();
const char *arch = cur_arch_name();
JsonValue *variants = json_get(meta, "variants");
const char *dl = NULL, *file = NULL;
char *want_sha = NULL;
const char *chosen = NULL;
if (variants && variants->count > 0) {
/* pick highest variant matching OS + ARCH (or 'any') + version spec */
const char *bestver = NULL;
for (int i = 0; i < variants->count; i++) {
JsonValue *v = json_at(variants, i);
if (!v || v->type != JSON_OBJECT) continue;
const char *vos = json_str(v, "os"); if (!vos) continue;
if (strcmp(vos, osn) != 0 && strcmp(vos, "any") != 0) continue;
const char *varch = json_str(v, "arch");
if (varch && varch[0] && strcmp(varch, arch) != 0 && strcmp(varch, "any") != 0) continue;
const char *ver = json_str(v, "version"); if (!ver) continue;
if (spec && *spec && !spec_match(ver, spec)) continue;
if (!bestver || vcmp(ver, bestver) > 0) { bestver = ver; chosen = ver;
dl = json_str(v, "url"); file = json_str(v, "file");
const char *s = json_str(v, "sha256"); free(want_sha); want_sha = s ? strdup(s) : NULL; }
}
if (!chosen) {
fprintf(stderr, "pmm: no version of '%s' for os=%s arch=%s%s%s\n", name, osn, arch,
(spec && *spec) ? " satisfying '" : "", (spec && *spec) ? spec : "");
json_free(meta); mirrors_free(ml); return -1;
}
printf("pmm: selected %s@%s (os=%s arch=%s)\n", name, chosen, osn, arch);
} else {
/* legacy single-platform entry */
dl = json_str(meta, "url"); file = json_str(meta, "file");
const char *s = json_str(meta, "sha256"); want_sha = s ? strdup(s) : NULL;
printf("pmm: selected %s@%s (os=%s)\n", name, json_str(meta, "version"), osn);
}
if (!dl) {
fprintf(stderr, "pmm: registry entry for '%s' has no url\n", name);
json_free(meta); mirrors_free(ml); return -1;
}
char *url_cp = strdup(dl);
char *file_cp = file ? strdup(file) : strdup(base_name(url_cp));
json_free(meta);
mirrors_free(ml);
int rc = install_file(url_cp, file_cp);
if (want_sha && rc == 0) {
char cache[1024], path[1200], hex[128];
pmm_cache_dir(cache, sizeof(cache));
snprintf(path, sizeof(path), "%s/%s", cache, file_cp);
if (pmm_sha256_file(path, hex) == 0) {
if (strcasecmp(want_sha, hex) != 0) {
fprintf(stderr, "pmm: CHECKSUM MISMATCH (%s registry entry)!\n"
" expected: %s\n actual: %s\n", name, want_sha, hex);
remove(path);
rc = -1;
} else {
printf("pmm: registry sha256 OK: %s\n", hex);
}
}
}
free(url_cp); free(file_cp); free(want_sha);
return rc;
}
static int install_dep_spec(const char *depstr) {
char name[256], spec[256];
if (parse_dep(depstr, name, sizeof(name), spec, sizeof(spec)) != 0) return -1;
for (int i = 0; i < dep_seen_n; i++)
if (strcmp(dep_seen[i], name) == 0) return 0; /* cycle / duplicate */
if (dep_seen_n < 128) dep_seen[dep_seen_n++] = strdup(name);
printf("pmm: resolving dependency %s%s%s\n", name, spec[0] ? " " : "", spec);
return install_from_registry(name, spec[0] ? spec : NULL, NULL);
}
+26
查看文件
@@ -0,0 +1,26 @@
/* install.h */
#ifndef PMM_INSTALL_H
#define PMM_INSTALL_H
/* Download `url` into cache and install it according to file type.
* name is the asset file name used to decide the install method. */
int install_file(const char *url, const char *name);
/* Install a local file path according to its extension — e.g.
* `pmm install -dpkg foo.deb` (Linux) or `pmm install -msi foo.msi` (Windows). */
int install_local_file(const char *path);
/* Set by `pmm install --no-cache` to force a fresh download (drop cache file). */
extern int pmm_no_cache;
/* Resolve a comma-separated Depends list ("a (>= 1.0), b") from the registry. */
int pmm_install_dep_list(const char *list);
/* Install from a remote registry (apt-style multi-mirror fallback):
* looks up package `name` in the configured registry mirrors by priority.
* If `version` is non-NULL, fetches {registry}/{name}/{version}.json, otherwise
* the latest pointer {registry}/{name}.json.
* mirror_active is the name of the explicitly selected mirror, or NULL. */
int install_from_registry(const char *name, const char *version, const char *mirror_active);
#endif
+222
查看文件
@@ -0,0 +1,222 @@
/* json.c - minimal recursive-descent JSON parser (no external deps) */
#include "json.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
typedef struct { const char *p; const char *end; } Parser;
static JsonValue *parse_value(Parser *ps);
static JsonValue *jnew(JsonType t) {
JsonValue *v = calloc(1, sizeof(JsonValue));
if (!v) { fprintf(stderr, "pmm: out of memory\n"); exit(1); }
v->type = t;
return v;
}
void json_free(JsonValue *v) {
if (!v) return;
free(v->string);
for (int i = 0; i < v->count; i++) {
if (v->keys) free(v->keys[i]);
json_free(v->items[i]);
}
free(v->keys);
free(v->items);
free(v);
}
static void jadd(JsonValue *v, char *key, JsonValue *item) {
if (v->count >= v->capacity) {
v->capacity = v->capacity ? v->capacity * 2 : 8;
v->items = realloc(v->items, v->capacity * sizeof(JsonValue *));
if (v->type == JSON_OBJECT)
v->keys = realloc(v->keys, v->capacity * sizeof(char *));
if (!v->items || (v->type == JSON_OBJECT && !v->keys)) {
fprintf(stderr, "pmm: out of memory\n"); exit(1);
}
}
if (v->type == JSON_OBJECT) v->keys[v->count] = key;
v->items[v->count++] = item;
}
static void skip_ws(Parser *ps) {
while (ps->p < ps->end && isspace((unsigned char)*ps->p)) ps->p++;
}
static void fail(const char *msg) {
fprintf(stderr, "pmm: JSON parse error: %s\n", msg);
exit(1);
}
static char *parse_string_raw(Parser *ps) {
if (ps->p >= ps->end || *ps->p != '"') fail("expected string");
ps->p++;
size_t cap = 16, len = 0;
char *buf = malloc(cap);
if (!buf) fail("out of memory");
while (ps->p < ps->end && *ps->p != '"') {
char c = *ps->p++;
char out[4]; int n = 1;
if (c == '\\') {
if (ps->p >= ps->end) fail("bad escape");
char e = *ps->p++;
switch (e) {
case '"': out[0] = '"'; break;
case '\\': out[0] = '\\'; break;
case '/': out[0] = '/'; break;
case 'b': out[0] = '\b'; break;
case 'f': out[0] = '\f'; break;
case 'n': out[0] = '\n'; break;
case 'r': out[0] = '\r'; break;
case 't': out[0] = '\t'; break;
case 'u': {
if (ps->end - ps->p < 4) fail("bad \\u escape");
unsigned cp = 0;
for (int i = 0; i < 4; i++) {
char h = *ps->p++;
cp <<= 4;
if (h >= '0' && h <= '9') cp |= (unsigned)(h - '0');
else if (h >= 'a' && h <= 'f') cp |= (unsigned)(h - 'a' + 10);
else if (h >= 'A' && h <= 'F') cp |= (unsigned)(h - 'A' + 10);
else fail("bad \\u escape");
}
/* UTF-8 encode (surrogate pairs not combined: good enough for names/URLs) */
if (cp < 0x80) { out[0] = (char)cp; n = 1; }
else if (cp < 0x800) { out[0] = (char)(0xC0 | (cp >> 6)); out[1] = (char)(0x80 | (cp & 0x3F)); n = 2; }
else { out[0] = (char)(0xE0 | (cp >> 12)); out[1] = (char)(0x80 | ((cp >> 6) & 0x3F)); out[2] = (char)(0x80 | (cp & 0x3F)); n = 3; }
break;
}
default: fail("bad escape");
}
} else {
out[0] = c;
}
if (len + (size_t)n + 1 > cap) {
cap *= 2;
buf = realloc(buf, cap);
if (!buf) fail("out of memory");
}
memcpy(buf + len, out, (size_t)n);
len += (size_t)n;
}
if (ps->p >= ps->end) fail("unterminated string");
ps->p++; /* closing quote */
buf[len] = '\0';
return buf;
}
static JsonValue *parse_number(Parser *ps) {
char *endp;
double d = strtod(ps->p, &endp);
if (endp == ps->p) fail("bad number");
ps->p = endp;
JsonValue *v = jnew(JSON_NUMBER);
v->number = d;
return v;
}
static JsonValue *parse_value(Parser *ps) {
skip_ws(ps);
if (ps->p >= ps->end) fail("unexpected end");
char c = *ps->p;
if (c == '{') {
ps->p++;
JsonValue *v = jnew(JSON_OBJECT);
skip_ws(ps);
if (ps->p < ps->end && *ps->p == '}') { ps->p++; return v; }
for (;;) {
skip_ws(ps);
char *key = parse_string_raw(ps);
skip_ws(ps);
if (ps->p >= ps->end || *ps->p != ':') fail("expected ':'");
ps->p++;
JsonValue *item = parse_value(ps);
jadd(v, key, item);
skip_ws(ps);
if (ps->p < ps->end && *ps->p == ',') { ps->p++; continue; }
if (ps->p < ps->end && *ps->p == '}') { ps->p++; return v; }
fail("expected ',' or '}'");
}
} else if (c == '[') {
ps->p++;
JsonValue *v = jnew(JSON_ARRAY);
skip_ws(ps);
if (ps->p < ps->end && *ps->p == ']') { ps->p++; return v; }
for (;;) {
JsonValue *item = parse_value(ps);
jadd(v, NULL, item);
skip_ws(ps);
if (ps->p < ps->end && *ps->p == ',') { ps->p++; continue; }
if (ps->p < ps->end && *ps->p == ']') { ps->p++; return v; }
fail("expected ',' or ']'");
}
} else if (c == '"') {
JsonValue *v = jnew(JSON_STRING);
v->string = parse_string_raw(ps);
return v;
} else if (c == 't') {
if (ps->end - ps->p < 4 || strncmp(ps->p, "true", 4)) fail("bad literal");
ps->p += 4;
JsonValue *v = jnew(JSON_BOOL); v->boolean = 1; return v;
} else if (c == 'f') {
if (ps->end - ps->p < 5 || strncmp(ps->p, "false", 5)) fail("bad literal");
ps->p += 5;
JsonValue *v = jnew(JSON_BOOL); return v;
} else if (c == 'n') {
if (ps->end - ps->p < 4 || strncmp(ps->p, "null", 4)) fail("bad literal");
ps->p += 4;
return jnew(JSON_NULL);
} else {
return parse_number(ps);
}
}
JsonValue *json_parse(const char *text) {
Parser ps = { text, text + strlen(text) };
JsonValue *v = parse_value(&ps);
return v;
}
char *read_whole_file(const char *path, long *out_len) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 0) { fclose(f); return NULL; }
char *buf = malloc((size_t)len + 1);
if (!buf) { fclose(f); return NULL; }
size_t rd = fread(buf, 1, (size_t)len, f);
fclose(f);
buf[rd] = '\0';
if (out_len) *out_len = (long)rd;
return buf;
}
JsonValue *json_parse_file(const char *path) {
char *text = read_whole_file(path, NULL);
if (!text) return NULL;
JsonValue *v = json_parse(text);
free(text);
return v;
}
JsonValue *json_get(const JsonValue *obj, const char *key) {
if (!obj || obj->type != JSON_OBJECT) return NULL;
for (int i = 0; i < obj->count; i++)
if (strcmp(obj->keys[i], key) == 0) return obj->items[i];
return NULL;
}
JsonValue *json_at(const JsonValue *arr, int i) {
if (!arr || arr->type != JSON_ARRAY || i < 0 || i >= arr->count) return NULL;
return arr->items[i];
}
const char *json_str(const JsonValue *obj, const char *key) {
JsonValue *v = json_get(obj, key);
return (v && v->type == JSON_STRING) ? v->string : NULL;
}
+31
查看文件
@@ -0,0 +1,31 @@
/* json.h - minimal JSON parser for PMM */
#ifndef PMM_JSON_H
#define PMM_JSON_H
typedef enum {
JSON_NULL, JSON_BOOL, JSON_NUMBER, JSON_STRING, JSON_ARRAY, JSON_OBJECT
} JsonType;
typedef struct JsonValue JsonValue;
struct JsonValue {
JsonType type;
int boolean;
double number;
char *string; /* decoded string (JSON_STRING) */
JsonValue **items; /* array/object members (values) */
char **keys; /* object member keys */
int count;
int capacity;
};
JsonValue *json_parse(const char *text);
JsonValue *json_parse_file(const char *path);
void json_free(JsonValue *v);
/* Object member lookup by key; returns NULL if missing or not an object. */
JsonValue *json_get(const JsonValue *obj, const char *key);
/* Array index; returns NULL if out of range. */
JsonValue *json_at(const JsonValue *arr, int i);
/* Convenience: nested get, e.g. json_path(v, "assets", 0-ish) not supported; use chain. */
const char *json_str(const JsonValue *obj, const char *key); /* NULL if absent */
#endif
+30
查看文件
@@ -0,0 +1,30 @@
/* mirrors.h - apt-style mirror list with priorities and download fallback */
#ifndef PMM_MIRRORS_H
#define PMM_MIRRORS_H
typedef struct Mirror {
char *name;
char *api; /* API base (rewrites github/gitlab/gitea API endpoints) */
char *download; /* download prefix (gh-proxy style: prefix + original URL) */
char *registry; /* apt-style registry mirror: serves {registry}/{pkg}.json */
int priority; /* lower = tried first (default 100) */
} Mirror;
typedef struct {
Mirror *items;
int count;
} MirrorList;
/* Load mirrors from ~/.pmm/mirror.ini / mirror.conf, sorted by priority. */
MirrorList *mirrors_load(void);
void mirrors_free(MirrorList *list);
/* If no mirror config exists, write a default mirror.ini (sz + main). */
void pmm_ensure_default_mirror(const char *dir);
/* Mirror whose name matches the active selection in pmm config, or NULL. */
Mirror *mirrors_active(const MirrorList *list, const char *active_name);
/* Build candidate download URLs for `url` (caller frees each string and array).
* Order: prefix-style mirrors by priority, then the original URL. */
char **mirrors_download_candidates(const MirrorList *list, const char *url, int *out_n);
#endif
+587
查看文件
@@ -0,0 +1,587 @@
/* pdm.c - .pdm pack/install/remove (deb-like, built on system tar) */
#include "pdm.h"
#include "pmm.h"
#include "install.h"
#include "sha256.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>
#include <io.h>
#include <inttypes.h>
#define PMM_MKDIR(p) _mkdir(p)
#define RMDIR(p) _rmdir(p)
#define PMM_POPEN_READ(cmd) _popen(cmd, "r")
#define PMM_PCLOSE_READ(p) _pclose(p)
#define GETCWD(p,n) (_getcwd((p),(n)))
#define CHDIR(p) _chdir(p)
#else
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#define PMM_MKDIR(p) mkdir((p), 0755)
#define RMDIR(p) rmdir(p)
#define PMM_POPEN_READ(cmd) popen(cmd, "r")
#define PMM_PCLOSE_READ(p) pclose(p)
#define GETCWD(p,n) getcwd((p),(n))
#define CHDIR(p) chdir(p)
#endif
/* ---------- small helpers ---------- */
static int ends_with(const char *s, const char *suf) {
size_t sl = strlen(s), xl = strlen(suf);
return sl >= xl && strcmp(s + sl - xl, suf) == 0;
}
static const char *base_name(const char *path) {
const char *a = strrchr(path, '/');
const char *b = strrchr(path, '\\');
const char *p = (a > b) ? a : b;
return p ? p + 1 : path;
}
static void mkdir_p(const char *path) {
char tmp[1024];
strncpy(tmp, path, sizeof(tmp) - 1);
tmp[sizeof(tmp) - 1] = '\0';
for (char *p = tmp + 1; *p; p++) {
if (*p == '/' || *p == '\\') {
char c = *p; *p = '\0'; PMM_MKDIR(tmp); *p = c;
}
}
PMM_MKDIR(tmp);
}
/* Convert backslashes to forward slashes (so cmd/bash don't treat "C:\x" as host). */
static void rmtree(const char *dir) {
/* Pure-C recursive delete: no shell, so no path-flavour (bsdtar/GNU) issues. */
#ifdef _WIN32
char patt[1200];
snprintf(patt, sizeof(patt), "%s\\*", dir);
struct _finddata_t fd;
intptr_t h = _findfirst(patt, &fd);
if (h != -1) {
do {
if (strcmp(fd.name, ".") == 0 || strcmp(fd.name, "..") == 0) continue;
char full[1400];
snprintf(full, sizeof(full), "%s\\%s", dir, fd.name);
if (fd.attrib & _A_SUBDIR) rmtree(full);
else remove(full);
} while (_findnext(h, &fd) == 0);
_findclose(h);
}
RMDIR(dir);
#else
DIR *d = opendir(dir);
if (!d) return;
struct dirent *e;
while ((e = readdir(d)) != NULL) {
if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
char full[1400];
snprintf(full, sizeof(full), "%s/%s", dir, e->d_name);
struct stat st;
if (stat(full, &st) == 0 && S_ISDIR(st.st_mode)) rmtree(full);
else remove(full);
}
closedir(d);
RMDIR(dir);
#endif
}
/* KEY: value lookup in control text; returns malloc'd value or NULL. */
static char *control_get(const char *text, const char *key) {
size_t klen = strlen(key);
const char *line = text;
while (line && *line) {
const char *eol = strchr(line, '\n');
size_t ll = eol ? (size_t)(eol - line) : strlen(line);
if (ll > klen && strncasecmp(line, key, klen) == 0 && line[klen] == ':') {
const char *v = line + klen + 1;
while (v < line + ll && (*v == ' ' || *v == '\t')) v++;
size_t vl = (size_t)(line + ll - v);
while (vl && (v[vl-1] == '\r' || v[vl-1] == ' ')) vl--;
char *out = malloc(vl + 1);
memcpy(out, v, vl);
out[vl] = '\0';
return out;
}
line = eol ? eol + 1 : NULL;
}
return NULL;
}
static char *read_file(const char *path, long *len) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = malloc((size_t)n + 1);
size_t rd = fread(buf, 1, (size_t)n, f);
fclose(f);
buf[rd] = '\0';
if (len) *len = (long)rd;
return buf;
}
/* ---------- pack ---------- */
int pdm_pack(const char *dir, const char *out) {
char cp_[1100];
snprintf(cp_, sizeof(cp_), "%s/pdm-control", dir);
char *control = read_file(cp_, NULL);
if (!control) {
fprintf(stderr, "pdm: %s has no pdm-control file\n", dir);
return -1;
}
char *pkg = control_get(control, "Package");
char *ver = control_get(control, "Version");
if (!pkg || !*pkg) {
fprintf(stderr, "pdm: pdm-control missing 'Package:' field\n");
free(control); return -1;
}
if (!ver || !*ver) ver = strdup("0.0.0");
/* Output defaults to <Package>_<Version>.pdm in the current directory.
* Everything below uses paths relative to the CWD so the shell never sees a
* Windows drive-letter path (which MSYS bash would mangle). */
char outbuf[1100];
if (!out) {
snprintf(outbuf, sizeof(outbuf), "%s_%s.pdm", pkg, ver);
out = outbuf;
}
const char *stage = ".pdm-stage";
char cmd[2800];
rmtree(stage);
PMM_MKDIR(stage);
/* control.tar.gz (contains pdm-control; packed from the source dir) */
snprintf(cmd, sizeof(cmd), "tar -czf \"%s/control.tar.gz\" -C \"%s\" pdm-control", stage, dir);
if (system(cmd) != 0) { fprintf(stderr, "pdm: tar (control) failed\n"); rmtree(stage); return -1; }
/* data.tar.gz (everything except pdm-control and the scratch dir) */
snprintf(cmd, sizeof(cmd),
"tar -czf \"%s/data.tar.gz\" -C \"%s\" --exclude pdm-control --exclude %s .",
stage, dir, stage);
if (system(cmd) != 0) { fprintf(stderr, "pdm: tar (data) failed\n"); rmtree(stage); return -1; }
/* sha256sums of the two members */
char sums_path[1100], hex[128];
snprintf(sums_path, sizeof(sums_path), "%s/sha256sums", stage);
FILE *sf = fopen(sums_path, "w");
if (sf) {
char mp[1200];
snprintf(mp, sizeof(mp), "%s/control.tar.gz", stage);
if (pmm_sha256_file(mp, hex) == 0) fprintf(sf, "%s control.tar.gz\n", hex);
snprintf(mp, sizeof(mp), "%s/data.tar.gz", stage);
if (pmm_sha256_file(mp, hex) == 0) fprintf(sf, "%s data.tar.gz\n", hex);
fclose(sf);
}
/* outer archive */
snprintf(cmd, sizeof(cmd), "tar -cf \"%s\" -C \"%s\" control.tar.gz data.tar.gz sha256sums", out, stage);
int rc = system(cmd);
rmtree(stage);
if (rc != 0) { fprintf(stderr, "pdm: failed to write %s\n", out); return -1; }
printf("pdm: packed %s (%s %s) -> %s\n", dir, pkg, ver, out);
free(control); free(pkg);
if (strcmp(ver, "0.0.0") != 0) free(ver);
return 0;
}
/* ---------- install ---------- */
static void db_dirs(char *db, size_t dbs, char *root, size_t roots) {
char home[900];
pmm_config_dir(home, sizeof(home) - 32);
snprintf(db, dbs, "%s/installed", home);
mkdir_p(db);
if (pmm_flat_mode()) snprintf(root, roots, "%s", home); /* -p <path> */
else snprintf(root, roots, "%s/root", home);
mkdir_p(root);
}
static int copy_file(const char *src, const char *dst) {
FILE *in = fopen(src, "rb");
if (!in) return -1;
FILE *out = fopen(dst, "wb");
if (!out) { fclose(in); return -1; }
char buf[65536];
size_t n;
while ((n = fread(buf, 1, sizeof(buf), in)) > 0)
if (fwrite(buf, 1, n, out) != n) { fclose(in); fclose(out); return -1; }
fclose(in);
fclose(out);
return 0;
}
/* Recursively find the first .exe under dir (for the app icon); malloc'd path or NULL. */
static char *find_first_exe(const char *dir) {
#ifdef _WIN32
char patt[1300];
snprintf(patt, sizeof(patt), "%s\\*", dir);
struct _finddata_t fd;
intptr_t h = _findfirst(patt, &fd);
if (h != -1) {
do {
if (strcmp(fd.name, ".") == 0 || strcmp(fd.name, "..") == 0) continue;
char full[1400];
snprintf(full, sizeof(full), "%s\\%s", dir, fd.name);
if ((fd.attrib & _A_SUBDIR)) {
char *r = find_first_exe(full);
if (r) { _findclose(h); return r; }
} else {
size_t l = strlen(fd.name);
if (l > 4 && strcasecmp(fd.name + l - 4, ".exe") == 0) {
char *r = malloc(1400);
if (r) snprintf(r, 1400, "%s\\%s", dir, fd.name);
_findclose(h);
return r;
}
}
} while (_findnext(h, &fd) == 0);
_findclose(h);
}
#else
DIR *d = opendir(dir);
if (d) {
struct dirent *e;
while ((e = readdir(d)) != NULL) {
if (e->d_name[0] == '.') continue;
char full[1400];
snprintf(full, sizeof(full), "%s/%s", dir, e->d_name);
struct stat st;
if (stat(full, &st) == 0) {
if (S_ISDIR(st.st_mode)) { char *r = find_first_exe(full); if (r) { closedir(d); return r; } }
}
}
closedir(d);
}
#endif
return NULL;
}
/* Recursively sum file sizes under dir, in bytes. */
static unsigned long long dir_size(const char *dir) {
unsigned long long total = 0;
#ifdef _WIN32
char patt[1300];
snprintf(patt, sizeof(patt), "%s\\*", dir);
struct _finddata_t fd;
intptr_t h = _findfirst(patt, &fd);
if (h != -1) {
do {
if (strcmp(fd.name, ".") == 0 || strcmp(fd.name, "..") == 0) continue;
char full[1400];
snprintf(full, sizeof(full), "%s\\%s", dir, fd.name);
if (fd.attrib & _A_SUBDIR) total += dir_size(full);
else total += (unsigned long long)fd.size;
} while (_findnext(h, &fd) == 0);
_findclose(h);
}
#else
DIR *d = opendir(dir);
if (d) {
struct dirent *e;
while ((e = readdir(d)) != NULL) {
if (e->d_name[0] == '.') continue;
char full[1400];
snprintf(full, sizeof(full), "%s/%s", dir, e->d_name);
struct stat st;
if (stat(full, &st) == 0) {
if (S_ISDIR(st.st_mode)) total += dir_size(full);
else total += (unsigned long long)st.st_size;
}
}
closedir(d);
}
#endif
return total;
}
static char saved_cwd[1400];
static int chdir_save(const char *dir) {
if (!GETCWD(saved_cwd, sizeof(saved_cwd))) saved_cwd[0] = '\0';
return CHDIR(dir) == 0 ? 0 : -1;
}
static void chdir_restore(void) {
if (saved_cwd[0]) CHDIR(saved_cwd);
}
/* Move everything under <dir>/bin up into <dir>, then remove bin/. (C-level, so
* it works on Windows cmd too where `mv` doesn't exist.) */
static void flatten_bin(const char *dir) {
char bindir[1200];
snprintf(bindir, sizeof(bindir), "%s/%s", dir, "bin");
#ifdef _WIN32
char patt[1300];
snprintf(patt, sizeof(patt), "%s\\*", bindir);
struct _finddata_t fd;
intptr_t h = _findfirst(patt, &fd);
if (h != -1) {
do {
if (strcmp(fd.name, ".") == 0 || strcmp(fd.name, "..") == 0) continue;
char src[1400], dst[1400];
snprintf(src, sizeof(src), "%s\\%s", bindir, fd.name);
snprintf(dst, sizeof(dst), "%s\\%s", dir, fd.name);
rename(src, dst);
} while (_findnext(h, &fd) == 0);
_findclose(h);
}
RMDIR(bindir);
#else
DIR *d = opendir(bindir);
if (d) {
struct dirent *e;
while ((e = readdir(d)) != NULL) {
if (e->d_name[0] == '.') continue;
char src[1400], dst[1400];
snprintf(src, sizeof(src), "%s/%s", bindir, e->d_name);
snprintf(dst, sizeof(dst), "%s/%s", dir, e->d_name);
rename(src, dst);
}
closedir(d);
rmdir(bindir);
}
#endif
}
int pdm_info(const char *pdmfile) {
printf("pdm: %s\n", base_name(pdmfile));
/* Copy into the pm dir + chdir, then use relative names (works with both
* MSYS GNU tar and System32 bsdtar). */
char home[1024];
pmm_config_dir(home, sizeof(home));
char info_tmp[1400];
snprintf(info_tmp, sizeof(info_tmp), "%s/_pmm_info.pdm", home);
if (copy_file(pdmfile, info_tmp) != 0) return -1;
if (chdir_save(home) != 0) { remove(info_tmp); return -1; }
char cmd[2800];
snprintf(cmd, sizeof(cmd), "tar -xOf _pmm_info.pdm control.tar.gz | tar -xzOf - pdm-control");
int rc = system(cmd);
chdir_restore();
remove(info_tmp);
return rc == 0 ? 0 : -1;
}
int pdm_install_file(const char *pdmfile) {
if (!ends_with(pdmfile, ".pdm")) {
fprintf(stderr, "pdm: '%s' is not a .pdm file\n", pdmfile);
return -1;
}
char home[1024], db[1024], root[1024], cmd[2600];
pmm_config_dir(home, sizeof(home));
int flat = pmm_flat_mode(); /* -p <path>: deploy directly into that dir */
snprintf(db, sizeof(db), "%s/installed", home);
mkdir_p(db);
if (flat)
snprintf(root, sizeof(root), "%s", home);
else
snprintf(root, sizeof(root), "%s/root", home);
mkdir_p(root);
/* Copy the package into the PM dir, chdir there, and run tar with ONLY
* relative paths. Relative paths are understood identically by MSYS GNU tar
* and System32 bsdtar, so we don't have to guess a drive-letter style. */
char tmpname[1400], tmprel[] = "_pmm_install.pdm";
snprintf(tmpname, sizeof(tmpname), "%s/_pmm_install.pdm", home);
if (copy_file(pdmfile, tmpname) != 0) {
fprintf(stderr, "pdm: cannot read %s\n", pdmfile);
return -1;
}
if (chdir_save(home) != 0) { remove(tmpname); return -1; }
if (system(NULL) == 0) { fprintf(stderr, "pdm: cannot run tar\n"); chdir_restore(); remove(tmpname); return -1; }
const char *stage = "installed/.stage";
rmtree(stage);
PMM_MKDIR(stage);
/* extract members (all relative) */
snprintf(cmd, sizeof(cmd), "tar -xf \"%s\" -C \"%s\"", tmprel, stage);
if (system(cmd) != 0) {
fprintf(stderr, "pdm: not a valid .pdm archive: %s\n", pdmfile);
chdir_restore(); remove(tmpname); return -1;
}
/* verify member checksums (relative to pm dir) */
char hex[128];
FILE *sf = fopen("installed/.stage/sha256sums", "r");
if (sf) {
char line[256], fname[128], expect[128];
while (fgets(line, sizeof(line), sf)) {
if (sscanf(line, "%127s %127s", expect, fname) != 2) continue;
char mp[1200];
snprintf(mp, sizeof(mp), "installed/.stage/%s", fname);
if (pmm_sha256_file(mp, hex) != 0 || strcasecmp(hex, expect) != 0) {
fprintf(stderr, "pdm: CHECKSUM MISMATCH for %s in %s\n", fname, pdmfile);
fclose(sf); chdir_restore(); remove(tmpname); return -1;
}
}
fclose(sf);
printf("pdm: sha256 checksums OK\n");
} else {
printf("pdm: warning: no sha256sums member, skipping integrity check\n");
}
/* read control */
snprintf(cmd, sizeof(cmd), "tar -xzOf \"%s/control.tar.gz\" pdm-control", stage);
FILE *cf = PMM_POPEN_READ(cmd);
if (!cf) {
fprintf(stderr, "pdm: missing control.tar.gz in %s\n", pdmfile);
chdir_restore(); remove(tmpname); return -1;
}
char ctl[4096];
size_t got = fread(ctl, 1, sizeof(ctl) - 1, cf);
PMM_PCLOSE_READ(cf);
ctl[got] = '\0';
/* resolve declared dependencies from the registry before installing */
char *deps = control_get(ctl, "Depends");
if (deps && *deps) { pmm_install_dep_list(deps); free(deps); }
char *pkg = control_get(ctl, "Package");
char *ver = control_get(ctl, "Version");
if (!pkg || !*pkg) {
fprintf(stderr, "pdm: package has no Package: field\n");
chdir_restore(); remove(tmpname); return -1;
}
/* extract data into root (relative target: cwd is already the pm dir) */
const char *tgt = flat ? "." : "root";
snprintf(cmd, sizeof(cmd), "tar -xzf \"%s/data.tar.gz\" -C \"%s\"", stage, tgt);
if (system(cmd) != 0) {
fprintf(stderr, "pdm: data extraction failed\n");
chdir_restore(); remove(tmpname); return -1;
}
/* flat mode (-p <path>): move bin/* up into the address itself, drop bin/ */
if (flat) {
flatten_bin("."); /* cwd = the install address in flat mode */
}
/* record file list + control in db */
snprintf(cmd, sizeof(cmd), "%s/%s.info", db, pkg);
FILE *dbf = fopen(cmd, "wb"); /* binary: keep \n, avoid Windows CRLF translation */
if (dbf) {
fprintf(dbf, "Package: %s\nVersion: %s\nSource: %s\n\n%s\n",
pkg, ver ? ver : "0.0.0", base_name(pdmfile), ctl);
fprintf(dbf, "Files:\n");
char ltcmd[2600];
snprintf(ltcmd, sizeof(ltcmd), "tar -tzf \"%s/data.tar.gz\"", stage);
FILE *tf = PMM_POPEN_READ(ltcmd);
if (tf) {
char ln[2048];
while (fgets(ln, sizeof(ln), tf)) {
if (flat) { /* reflection of the flatten: strip a leading bin/ */
const char *q = ln;
while (*q==' '||*q=='\t') q++;
if (q[0]=='.'&&q[1]=='/'&&strncasecmp(q+2,"bin/",4)==0) { ln[0]='\0'; continue; }
}
fputs(ln, dbf);
}
PMM_PCLOSE_READ(tf);
} else {
fprintf(stderr, "pdm: warning: could not record file list\n");
}
fclose(dbf);
}
rmtree(stage);
chdir_restore();
remove(tmpname);
printf("pdm: installed %s %s into %s\n", pkg, ver ? ver : "0.0.0", root);
/* register in the Windows per-user "Installed Apps" (Uninstall) hive,
* like an MSI installer would, so it shows in Settings > Installed apps */
{
char rootpkg[1400];
snprintf(rootpkg, sizeof(rootpkg), "%s/%s", root, pkg);
char *icon = find_first_exe(rootpkg);
const char *pub = control_get(ctl, "Maintainer");
const char *self = pmm_self_path();
pmm_reg_uninstall(pkg, ver ? ver : "", pub ? pub : "",
root, icon, self && *self ? self : "pmm",
dir_size(rootpkg));
if (pub) free((char *)pub);
free(icon);
}
pmm_add_to_path(); /* auto-put PMM dirs (e.g. root/nodejs) on PATH */
#ifndef _WIN32
/* On Linux/macOS, mark anything in <root>/bin executable. Windows doesn't
* record unix exec bits, so ELF/scripts packaged on Windows lose +x; restore
* them at install time so e.g. a Linux Node.js binary actually runs. */
{
char cx[1600];
snprintf(cx, sizeof(cx), "chmod +x \"%s/bin\"/* 2>/dev/null || true", root);
system(cx);
}
#endif
free(pkg); if (ver) free(ver);
return 0;
}
/* ---------- list / remove ---------- */
void pdm_list_installed(void) {
char db[1024], root[1024];
db_dirs(db, sizeof(db), root, sizeof(root));
#ifdef _WIN32
char cmd[1200];
snprintf(cmd, sizeof(cmd), "dir /b \"%s\\*.info\" 2>nul", db);
#else
char cmd[1200];
snprintf(cmd, sizeof(cmd), "ls -1 \"%s\"/*.info 2>/dev/null", db);
#endif
printf("installed .pdm packages (root: %s):\n", root);
fflush(stdout);
system(cmd);
}
int pdm_remove(const char *name) {
char db[1024], root[1024];
db_dirs(db, sizeof(db), root, sizeof(root));
char ipath[1100];
snprintf(ipath, sizeof(ipath), "%s/%s.info", db, name);
long len = 0;
char *info = read_file(ipath, &len);
if (!info) {
fprintf(stderr, "pdm: package '%s' is not installed\n", name);
return -1;
}
char *files = strstr(info, "Files:\n");
int n = 0;
if (files) {
char path[1024];
char *line = files + 7;
while (*line) {
char *eol = strchr(line, '\n');
if (!eol) eol = line + strlen(line);
size_t ll = (size_t)(eol - line);
while (ll && (line[ll-1] == '\r' || line[ll-1] == ' ')) ll--;
if (ll > 0 && ll < sizeof(path)) {
memcpy(path, line, ll); path[ll] = '\0';
/* normalize "./x" -> "x" */
char *p = path;
while (p[0] == '.' && p[1] == '/') p += 2;
char full[1200];
snprintf(full, sizeof(full), "%s/%s", root, p);
remove(full);
n++;
}
line = *eol ? eol + 1 : eol;
}
}
remove(ipath);
free(info);
printf("pdm: removed %s (%d files)\n", name, n);
pmm_reg_uninstall_clear(name); /* drop it from Installed Apps */
return 0;
}
+37
查看文件
@@ -0,0 +1,37 @@
/* pdm.h - .pdm package format (deb-like)
*
* A .pdm file is a tar archive with three members:
* control.tar.gz -> contains "pdm-control" text file (Package/Version/...)
* data.tar.gz -> the package payload, extracted into the pm root
* sha256sums -> "<hex> control.tar.gz" lines for both members
*
* pdm-control fields (KEY: value lines):
* Package: name
* Version: 1.0.0
* Architecture: all | windows | linux | macos
* Description: text
* Maintainer: text
*
* The folder passed to `pdm pack ./dir` must contain a "pdm-control" file;
* everything else in the folder becomes data.tar.gz.
*/
#ifndef PMM_PDM_H
#define PMM_PDM_H
/* Pack directory (containing pdm-control) into out.pdm. out may be NULL
* (defaults to <Package>_<Version>.pdm next to the dir). */
int pdm_pack(const char *dir, const char *out);
/* Install a local .pdm into root dir (~/.pmm/root), record in db (~/.pmm/installed). */
int pdm_install_file(const char *pdmfile);
/* Remove installed package by name (dpkg -r style: delete data files). */
int pdm_remove(const char *name);
/* Print installed packages from the db. */
void pdm_list_installed(void);
/* Print control fields of a .pdm file. */
int pdm_info(const char *pdmfile);
#endif
+381
查看文件
@@ -0,0 +1,381 @@
/* repo.c - git hosting adapters: GitHub / GitLab / Gitea / Forgejo
*
* API endpoints used (latest release):
* GitHub: {api}/repos/{repo}/releases/latest
* GitLab: {api}/projects/{url-encoded repo}/releases/permalink/latest
* Gitea/Forgejo: {api}/api/v1/repos/{repo}/releases/latest
*
* Asset selection by OS (first match wins, in priority order):
* windows: .exe .msi .zip .7z
* linux: .deb .rpm .appimage .tar.gz .tgz .tar.xz
* macos: .dmg .pkg .app.zip .tar.gz
*/
#include "repo.h"
#include "http.h"
#include "json.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
static char *strdup_or(const char *s, const char *dflt) {
return strdup(s ? s : dflt);
}
static char *url_encode(const char *s) {
size_t cap = strlen(s) * 3 + 1;
char *out = malloc(cap);
size_t o = 0;
for (const char *p = s; *p && o + 4 < cap; p++) {
unsigned char c = (unsigned char)*p;
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
out[o++] = (char)c;
} else if (c == '/') {
out[o++] = '%'; out[o++] = '2'; out[o++] = 'F';
} else {
char tmp[8];
snprintf(tmp, sizeof(tmp), "%%%02X", c);
out[o++] = tmp[0]; out[o++] = tmp[1]; out[o++] = tmp[2];
}
}
out[o] = '\0';
return out;
}
/* Normalize "https://host/owner/repo(.git)" or "owner/repo" into "owner/repo". */
static char *normalize_repo(const char *in) {
const char *s = in;
int is_url = 0;
if (strncmp(s, "https://", 8) == 0) { s += 8; is_url = 1; }
else if (strncmp(s, "http://", 7) == 0) { s += 7; is_url = 1; }
/* skip hostname only for full URLs */
if (is_url) {
const char *slash = strchr(s, '/');
if (slash && slash != s) s = slash + 1;
}
size_t len = strlen(s);
if (len > 4 && strcmp(s + len - 4, ".git") == 0) len -= 4;
/* strip trailing slashes */
while (len > 0 && s[len - 1] == '/') len--;
char *out = malloc(len + 1);
memcpy(out, s, len);
out[len] = '\0';
return out;
}
PmmHost repo_detect_host(const char *repo_or_url) {
if (strstr(repo_or_url, "github.com")) return HOST_GITHUB;
if (strstr(repo_or_url, "gitlab.")) return HOST_GITLAB;
if (strstr(repo_or_url, "gitea.")) return HOST_GITEA;
if (strstr(repo_or_url, "forgejo.")) return HOST_FORGEJO;
/* full URL with unknown host: try to infer from path; default gitea-compatible */
if (strncmp(repo_or_url, "http", 4) == 0) return HOST_GITEA;
return HOST_GITHUB; /* bare slug defaults to github */
}
/* Extract "scheme://host[:port]" from a URL; empty string if none. */
static void extract_origin(const char *repo, char *origin, size_t size) {
const char *s = repo;
origin[0] = '\0';
if (strncmp(s, "https://", 8) == 0) s += 8;
else if (strncmp(s, "http://", 7) == 0) { s += 7; }
else return; /* bare slug: no origin */
s -= (strncmp(repo, "https://", 8) == 0) ? 8 : 7;
const char *slash = strchr(s + 8, '/');
if (!slash) slash = s + strlen(s);
size_t n = (size_t)(slash - s);
if (n >= size) n = size - 1;
memcpy(origin, s, n);
origin[n] = '\0';
}
RepoContext *repo_open(PmmHost host, const char *repo, const char *mirror_api_base) {
RepoContext *ctx = calloc(1, sizeof(RepoContext));
if (!ctx) return NULL;
ctx->host = host;
/* support "repo@ref" (e.g. owner/repo@v1.2.3) → specific release tag */
const char *lastslash = strrchr(repo, '/');
const char *at = strrchr(repo, '@');
char repo_buf[1024];
const char *repo_src = repo;
if (at && lastslash && at > lastslash) {
snprintf(repo_buf, sizeof(repo_buf), "%.*s", (int)(at - repo), repo);
repo_src = repo_buf;
ctx->ref = strdup(at + 1);
} else {
ctx->ref = strdup("");
}
char *norm = normalize_repo(repo_src);
char api[1024];
ctx->repo_norm = strdup(norm);
char origin[512];
extract_origin(repo_src, origin, sizeof(origin));
ctx->origin = strdup(origin);
const char *base = NULL;
if (mirror_api_base && *mirror_api_base) {
base = mirror_api_base; /* mirror overrides host entirely */
} else {
switch (host) {
case HOST_GITHUB: base = "https://api.github.com"; break;
case HOST_GITLAB: base = "https://gitlab.com"; break;
default: base = NULL; break; /* gitea/forgejo/auto need a full URL */
}
}
if (host == HOST_AUTO) {
/* Generic git URL: api_url is built per-candidate in repo_latest_asset */
ctx->page_url = strdup_or(origin[0] ? origin : "", "");
ctx->api_url = NULL;
free(norm);
return ctx;
}
if (!base) {
/* Gitea/Forgejo require a full URL in the repo argument, e.g.
* https://git.example.com/owner/repo or configured in mirror file. */
if (!origin[0])
strncpy(origin, "http://localhost:3000", sizeof(origin) - 1);
snprintf(api, sizeof(api), "%s/api/v1/repos/%s", origin, norm);
ctx->page_url = strdup_or(origin, "");
} else {
switch (host) {
case HOST_GITHUB:
snprintf(api, sizeof(api), "%s/repos/%s", base, norm);
break;
case HOST_GITLAB: {
char *enc = url_encode(norm);
snprintf(api, sizeof(api), "%s/projects/%s", base, enc);
free(enc);
break;
}
default: /* gitea/forgejo via mirror base */
snprintf(api, sizeof(api), "%s/api/v1/repos/%s", base, norm);
break;
}
ctx->page_url = strdup(base);
}
ctx->api_url = strdup(api);
free(norm);
return ctx;
}
void repo_close(RepoContext *ctx) {
if (!ctx) return;
free(ctx->api_url);
free(ctx->page_url);
free(ctx->origin);
free(ctx->repo_norm);
free(ctx->ref);
free(ctx);
}
static int has_ext(const char *name, const char *ext) {
size_t nl = strlen(name), el = strlen(ext);
if (nl < el) return 0;
const char *tail = name + nl - el;
if (strcasecmp(tail, ext) != 0) return 0;
/* ".tar.gz" style: for ".gz" only match after ".tar"? keep simple: exact suffix */
return 1;
}
static int asset_is_junk(const char *name);
/* Case-insensitive substring test (avoids relying on non-standard strcasestr). */
static int ci_contains(const char *hay, const char *needle) {
size_t nl = strlen(needle);
if (!nl) return 0;
for (; *hay; hay++) {
size_t i;
for (i = 0; i < nl && hay[i]; i++)
if (tolower((unsigned char)hay[i]) != tolower((unsigned char)needle[i])) break;
if (i == nl) return 1;
}
return 0;
}
/* True if the asset filename clearly belongs to ANOTHER platform (e.g. an
* "android"/"windows"/"darwin" tar.gz must not be chosen for Linux). */
static int name_hints_other_os(const char *name, PmmOS os) {
if (!name) return 0;
if (os == OS_LINUX)
return ci_contains(name,"android") || ci_contains(name,"windows") ||
ci_contains(name,"win32") || ci_contains(name,"darwin") ||
ci_contains(name,"macos") || ci_contains(name,"osx");
if (os == OS_WINDOWS)
return ci_contains(name,"linux") || ci_contains(name,"darwin") ||
ci_contains(name,"android") || ci_contains(name,"macos") ||
ci_contains(name,"osx");
if (os == OS_MACOS)
return ci_contains(name,"linux") || ci_contains(name,"windows") ||
ci_contains(name,"win32") || ci_contains(name,"android");
return 0;
}
int asset_matches_os(const char *filename, PmmOS os) {
static const char *win_exts[] = { ".exe", ".msi", ".zip", ".7z", NULL };
static const char *linux_exts[] = { ".deb", ".rpm", ".appimage", ".tar.gz", ".tgz", ".tar.xz", ".tar.bz2", ".pkg.tar.zst", NULL };
static const char *mac_exts[] = { ".dmg", ".pkg", ".app.zip", ".tar.gz", ".tgz", NULL };
const char **exts = NULL;
switch (os) {
case OS_WINDOWS: exts = win_exts; break;
case OS_LINUX: exts = linux_exts; break;
case OS_MACOS: exts = mac_exts; break;
default: return 0;
}
/* Never pick an asset whose name names a different platform (fzf publishes
* xxxx-android_<arch>.tar.gz; a Linux user must not get that). */
if (name_hints_other_os(filename, os)) return 0;
for (int i = 0; exts[i]; i++)
if (has_ext(filename, exts[i])) return 1;
/* A bare (no-dot) binary counts on Linux/macOS — e.g. a release asset named
* "pmm" that is an ELF. Skip obvious junk like README/LICENCE/checksums. */
if ((os == OS_LINUX || os == OS_MACOS) && strchr(filename, '.') == NULL
&& !asset_is_junk(filename))
return 1;
return 0;
}
/* Reject common junk assets like sources, checksums, sigs. */
static int asset_is_junk(const char *name) {
static const char *junk[] = { ".sha256", ".sha512", ".sig", ".asc", ".sbom",
".json", ".txt", ".yml", ".yaml", ".xml", ".sb", ".pem", NULL };
for (int i = 0; junk[i]; i++)
if (has_ext(name, junk[i])) return 1;
if (strstr(name, "checksum") || strstr(name, "source") || strstr(name, "-src")) return 1;
return 0;
}
static ReleaseAsset *asset_from_json(const JsonValue *a, const char *tag) {
ReleaseAsset *ra = calloc(1, sizeof(ReleaseAsset));
const char *name = json_str(a, "name");
const char *url = json_str(a, "browser_download_url");
if (!url) url = json_str(a, "direct_asset_url");
ra->name = strdup_or(name, "asset");
ra->url = strdup_or(url, "");
ra->tag = strdup_or(tag, "");
return ra;
}
static ReleaseAsset *pick_from_assets_array(const JsonValue *assets, const char *tag, PmmOS os, const char *name_sub) {
for (int i = 0; i < assets->count; i++) {
JsonValue *a = json_at(assets, i);
if (!a) continue;
const char *name = json_str(a, "name");
if (!name || asset_is_junk(name)) continue;
if (!asset_matches_os(name, os)) continue;
if (name_sub && !ci_contains(name, name_sub)) continue;
return asset_from_json(a, tag);
}
return NULL;
}
static ReleaseAsset *fetch_latest(const char *url, PmmHost host, PmmOS os, const char *name_sub) {
int status = 0;
char *body = http_get(url, &status);
if (!body || (status != 200 && status != 0)) {
free(body);
return NULL;
}
JsonValue *root = json_parse(body);
free(body);
if (!root) return NULL;
const char *tag = json_str(root, "tag_name");
if (!tag) tag = json_str(root, "tag");
ReleaseAsset *found = NULL;
/* GitHub: assets is at root; Gitea: same; GitLab: assets.links[] */
JsonValue *assets = json_get(root, "assets");
if (host == HOST_GITLAB && assets) {
JsonValue *links = json_get(assets, "links");
if (links) {
for (int i = 0; i < links->count && !found; i++) {
JsonValue *a = json_at(links, i);
const char *name = json_str(a, "name");
if (!name) name = json_str(a, "direct_asset_url");
if (!name || asset_is_junk(name) || !asset_matches_os(name, os)) continue;
if (name_sub && !ci_contains(name, name_sub)) continue;
found = calloc(1, sizeof(ReleaseAsset));
found->name = strdup(name);
found->url = strdup_or(json_str(a, "direct_asset_url"), json_str(a, "url") ? json_str(a, "url") : "");
found->tag = strdup_or(tag, "");
}
}
} else if (assets) {
found = pick_from_assets_array(assets, tag, os, name_sub);
}
json_free(root);
if (found && (!found->url || !*found->url)) {
repo_asset_free(found);
return NULL;
}
return found;
}
ReleaseAsset *repo_latest_asset(RepoContext *ctx, PmmOS os) {
char url[2048];
if (ctx->host == HOST_AUTO) {
/* Generic git repo: probe known API shapes on the given origin. */
const char *origin = (ctx->origin && *ctx->origin) ? ctx->origin : NULL;
const char *norm = ctx->repo_norm;
ReleaseAsset *a = NULL;
if (origin) {
snprintf(url, sizeof(url), "%s/api/v1/repos/%s/releases/latest", origin, norm);
if ((a = fetch_latest(url, HOST_GITEA, os, NULL))) { ctx->host = HOST_GITEA; return a; }
char *enc = url_encode(norm);
snprintf(url, sizeof(url), "%s/api/v4/projects/%s/releases/permalink/latest", origin, enc);
free(enc);
if ((a = fetch_latest(url, HOST_GITLAB, os, NULL))) { ctx->host = HOST_GITLAB; return a; }
if (strstr(origin, "github.com")) {
snprintf(url, sizeof(url), "https://api.github.com/repos/%s/releases/latest", norm);
if ((a = fetch_latest(url, HOST_GITHUB, os, NULL))) { ctx->host = HOST_GITHUB; return a; }
}
} else {
snprintf(url, sizeof(url), "https://api.github.com/repos/%s/releases/latest", norm);
if ((a = fetch_latest(url, HOST_GITHUB, os, NULL))) { ctx->host = HOST_GITHUB; return a; }
snprintf(url, sizeof(url), "https://gitlab.com/api/v4/projects/%s/releases/permalink/latest", url_encode(norm));
if ((a = fetch_latest(url, HOST_GITLAB, os, NULL))) { ctx->host = HOST_GITLAB; return a; }
}
return NULL;
}
switch (ctx->host) {
case HOST_GITLAB:
if (ctx->ref && *ctx->ref) { char *e = url_encode(ctx->ref); snprintf(url, sizeof(url), "%s/releases/%s", ctx->api_url, e); free(e); }
else snprintf(url, sizeof(url), "%s/releases/permalink/latest", ctx->api_url);
break;
default:
if (ctx->ref && *ctx->ref) { char *e = url_encode(ctx->ref); snprintf(url, sizeof(url), "%s/releases/tags/%s", ctx->api_url, e); free(e); }
else snprintf(url, sizeof(url), "%s/releases/latest", ctx->api_url);
break;
}
return fetch_latest(url, ctx->host, os, NULL);
}
/* (Re)fetch the latest release and return the first asset for this OS whose
* name contains `name_sub` (e.g. "linux"). Used as a fallback when the first
* pick turns out to be a cross-platform/wrong binary. Caller frees via
* repo_asset_free. */
ReleaseAsset *repo_asset_matching(RepoContext *ctx, PmmOS os, const char *name_sub) {
if (!ctx || !ctx->api_url || !name_sub) return NULL;
char url[2048];
if (ctx->host == HOST_GITLAB) {
if (ctx->ref && *ctx->ref) { char *e = url_encode(ctx->ref); snprintf(url, sizeof(url), "%s/releases/%s", ctx->api_url, e); free(e); }
else snprintf(url, sizeof(url), "%s/releases/permalink/latest", ctx->api_url);
} else {
if (ctx->ref && *ctx->ref) { char *e = url_encode(ctx->ref); snprintf(url, sizeof(url), "%s/releases/tags/%s", ctx->api_url, e); free(e); }
else snprintf(url, sizeof(url), "%s/releases/latest", ctx->api_url);
}
return fetch_latest(url, ctx->host, os, name_sub);
}
void repo_asset_free(ReleaseAsset *a) {
if (!a) return;
free(a->url); free(a->name); free(a->tag);
free(a);
}
+89
查看文件
@@ -0,0 +1,89 @@
/* sha1.c - FIPS 180-4 SHA-1 */
#include "sha1.h"
#include <stdio.h>
#include <string.h>
#define ROTL(x,n) (((x) << (n)) | ((x) >> (32 - (n))))
static void transform(PmmSha1 *c, const unsigned char block[64]) {
unsigned int w[80], a, b, d, e, f, k, tmp;
unsigned int cc = c->state[2]; /* avoid shadowing */
for (int i = 0, j = 0; i < 16; i++, j += 4)
w[i] = ((unsigned int)block[j] << 24) | ((unsigned int)block[j+1] << 16) |
((unsigned int)block[j+2] << 8) | (unsigned int)block[j+3];
for (int i = 16; i < 80; i++)
w[i] = ROTL(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);
a = c->state[0]; b = c->state[1]; unsigned int c2 = cc; d = c->state[3]; e = c->state[4];
for (int i = 0; i < 80; i++) {
if (i < 20) { f = (b & c2) | ((~b) & d); k = 0x5A827999; }
else if (i < 40) { f = b ^ c2 ^ d; k = 0x6ED9EBA1; }
else if (i < 60) { f = (b & c2) | (b & d) | (c2 & d); k = 0x8F1BBCDC; }
else { f = b ^ c2 ^ d; k = 0xCA62C1D6; }
tmp = ROTL(a, 5) + f + e + k + w[i];
e = d; d = c2; c2 = ROTL(b, 30); b = a; a = tmp;
}
c->state[0]+=a; c->state[1]+=b; c->state[2]+=c2; c->state[3]+=d; c->state[4]+=e;
}
void pmm_sha1_init(PmmSha1 *c) {
c->count = 0;
c->state[0]=0x67452301; c->state[1]=0xEFCDAB89; c->state[2]=0x98BADCFE;
c->state[3]=0x10325476; c->state[4]=0xC3D2E1F0;
}
void pmm_sha1_update(PmmSha1 *c, const unsigned char *data, size_t len) {
size_t i = 0, j = (size_t)(c->count & 63);
c->count += len;
if (j + len > 63) {
size_t fill = 64 - j;
memcpy(c->buffer + j, data, fill);
transform(c, c->buffer);
for (i = fill; i + 63 < len; i += 64)
transform(c, data + i);
j = 0;
}
memcpy(c->buffer + j, data + i, len - i);
}
void pmm_sha1_final(PmmSha1 *c, unsigned char out[20]) {
unsigned char finalcount[8];
unsigned long long bits = c->count * 8; /* bit length before padding */
for (int i = 0; i < 8; i++)
finalcount[i] = (unsigned char)((bits >> (56 - 8 * i)) & 0xFF);
unsigned char pad = 0x80;
pmm_sha1_update(c, &pad, 1);
pad = 0x00;
while ((c->count & 63) != 56)
pmm_sha1_update(c, &pad, 1);
pmm_sha1_update(c, finalcount, 8);
for (int i = 0; i < 5; i++) {
out[i*4] = (unsigned char)(c->state[i] >> 24);
out[i*4+1] = (unsigned char)(c->state[i] >> 16);
out[i*4+2] = (unsigned char)(c->state[i] >> 8);
out[i*4+3] = (unsigned char)(c->state[i]);
}
}
void pmm_sha1_hex(const unsigned char digest[20], char *hex) {
static const char *d = "0123456789abcdef";
for (int i = 0; i < 20; i++) {
hex[i*2] = d[digest[i] >> 4];
hex[i*2+1] = d[digest[i] & 15];
}
hex[40] = '\0';
}
int pmm_sha1_file(const char *path, char *hex) {
FILE *f = fopen(path, "rb");
if (!f) return -1;
PmmSha1 c;
unsigned char buf[65536], digest[20];
size_t n;
pmm_sha1_init(&c);
while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
pmm_sha1_update(&c, buf, n);
fclose(f);
pmm_sha1_final(&c, digest);
pmm_sha1_hex(digest, hex);
return 0;
}
+18
查看文件
@@ -0,0 +1,18 @@
/* sha1.h */
#ifndef PMM_SHA1_H
#define PMM_SHA1_H
#include <stddef.h>
typedef struct {
unsigned int state[5];
unsigned long long count;
unsigned char buffer[64];
} PmmSha1;
void pmm_sha1_init(PmmSha1 *c);
void pmm_sha1_update(PmmSha1 *c, const unsigned char *data, size_t len);
void pmm_sha1_final(PmmSha1 *c, unsigned char out[20]);
int pmm_sha1_file(const char *path, char *hex); /* 41 bytes incl NUL */
void pmm_sha1_hex(const unsigned char digest[20], char *hex);
#endif

某些文件未显示,因为此 diff 中更改的文件太多 显示更多