镜像自地址
https://github.com/JGZYES/ParlzPackageManger.git
已同步 2026-09-11 05:42:44 +08:00
import source
这个提交包含在:
+3
@@ -27,3 +27,6 @@
|
||||
!web/downloads/
|
||||
!web/downloads/**/*.zip
|
||||
!web/downloads/**/*.deb
|
||||
|
||||
# source browser cache clone
|
||||
web/.src/
|
||||
|
||||
@@ -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\"}"}
|
||||
@@ -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\"}"}
|
||||
@@ -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\"}"}
|
||||
@@ -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
|
||||
@@ -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).
|
||||
|
||||
[](https://github.com/php/php-src/actions/workflows/push.yml)
|
||||
[](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
|
||||
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
@@ -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>.
|
||||
文件差异内容过多而无法显示
加载差异
二进制文件未显示。
@@ -0,0 +1 @@
|
||||
"%~dp0php.exe" "%~dp0pharcommand.phar" %*
|
||||
二进制文件未显示。
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
文件差异内容过多而无法显示
加载差异
@@ -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.
|
||||
@@ -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/apache:DirectoryIndex 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.0(Windows,源自官方 MSI)
|
||||
- `nodejs-linux-24.20.0.pdm` — Node.js 24.20.0(Linux x64,源自官方 .tar.xz)
|
||||
- `git-2.55.0.5.pdm` — Git for Windows 2.55.0.5(MinGit 官方发行版)
|
||||
- `pureftpd-1.0.51.pdm` — **Pure-FTPd 1.0.51(Linux)**:源码分发,装到 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-256(FIPS 180-4)
|
||||
src/sha1.c/.h SHA-1(FIPS 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)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# PMM 镜像源配置 (与 mirror.ini 格式完全相同)
|
||||
[ghfast]
|
||||
api = https://ghfast.example-mirror.dev
|
||||
default = true
|
||||
|
||||
[mygitea]
|
||||
api = https://git.mycompany.local
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
Package: hello
|
||||
Version: 1.0.0
|
||||
Architecture: all
|
||||
Description: A demo .pdm package
|
||||
Maintainer: PMM example <example@pmm.dev>
|
||||
@@ -0,0 +1,3 @@
|
||||
# PMM 配置 (与 pmm.json / pmm.ini 等价)
|
||||
registry = https://packages.example.com/pmm
|
||||
mirror = ghfast
|
||||
@@ -0,0 +1,3 @@
|
||||
# PMM 配置 (与 pmm.json 等价,二选一,查找顺序: pmm.json > pmm.ini > pmm.conf)
|
||||
registry = https://packages.example.com/pmm
|
||||
mirror = ghfast
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"registry": "https://packages.example.com/pmm",
|
||||
"mirror": "ghfast"
|
||||
}
|
||||
+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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 中更改的文件太多 显示更多
在新工单中引用
屏蔽一个用户