Skip to content

Commit b85d300

Browse files
eypsilonclaude
andcommitted
Add comprehensive testing infrastructure and security documentation
## Testing Infrastructure - Add PHPUnit test suite with 19 passing tests - MessageTest: 13 tests covering all Message class methods - AttachmentTest: 6 tests for attachment handling - Add phpunit.xml configuration - Add composer scripts for testing and analysis - test, test:verbose, test:coverage, test:filter - analyse, analyse:baseline - check (runs PHPStan + PHPUnit) ## Documentation - Add CONTRIBUTING.md with development guidelines - Add SECURITY.md with credential management best practices - Update README.md with Security and Contributing sections ## Code Quality - Fix ServiceConfig indentation issues (GitHub security finding) - Fix ImapService uninitialized variable (PHPStan level 8) - Add minimum-stability to composer.json ## Demo Improvements - Fix Collapse All button state to match initial collapsed messages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent e930338 commit b85d300

File tree

11 files changed

+563
-10
lines changed

11 files changed

+563
-10
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ php-ymap.code-workspace
44
ISSUES.md
55
.idea/
66
node_modules/
7+
admyn_commands/
78
coverage/
89
.phpunit.result.cache
910
phpstan.cache
1011
*.log
1112
composer.lock
13+
AI-REVIEWS.md
14+
TASK_LIST.md

CONTRIBUTING.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Contributing to php-ymap
2+
3+
Thank you for considering contributing to php-ymap! This document provides guidelines for contributing to the project.
4+
5+
## Getting Started
6+
7+
1. Fork the repository on GitHub
8+
2. Clone your fork locally
9+
3. Create a new branch for your feature or bugfix
10+
4. Make your changes
11+
5. Push to your fork and submit a pull request
12+
13+
## Development Requirements
14+
15+
- PHP 8.1 or higher
16+
- Composer
17+
- IMAP extension enabled
18+
- Extensions: iconv, json, mbstring
19+
20+
## Code Standards
21+
22+
- PHP 8.1+ syntax and features
23+
- Strict typing: `declare(strict_types=1)` in all files
24+
- Type hints for all parameters and return values
25+
- PHPStan level 8 compliance (run `vendor/bin/phpstan analyse`)
26+
27+
## Testing
28+
29+
When adding new features or fixing bugs:
30+
- Add tests if a test suite exists
31+
- Manually test with real IMAP servers when possible
32+
- Test edge cases (multipart messages, different encodings, inline attachments)
33+
34+
## Pull Request Guidelines
35+
36+
1. **One feature/fix per PR** - Keep pull requests focused
37+
2. **Update CHANGELOG.md** - Document your changes under `[Unreleased]`
38+
3. **Write clear commit messages** - Explain what and why, not just what
39+
4. **Test your changes** - Ensure PHPStan passes and functionality works
40+
5. **Update documentation** - Update README.md if adding new features
41+
42+
## Commit Message Format
43+
44+
```
45+
Brief summary (50 chars or less)
46+
47+
More detailed explanation if needed. Explain what changed
48+
and why, not just what was done.
49+
50+
- Bullet points for multiple changes
51+
- Keep lines under 72 characters
52+
```
53+
54+
## What to Contribute
55+
56+
We welcome:
57+
58+
- **Bug fixes** - Especially MIME parsing edge cases
59+
- **Documentation improvements** - Examples, clarifications, fixes
60+
- **Performance optimizations** - With benchmarks/profiling data
61+
- **Test coverage** - Unit tests for message parsing, attachments, encodings
62+
- **Security improvements** - TLS handling, input validation
63+
64+
Please open an issue first for:
65+
- Major architectural changes
66+
- New features that expand scope significantly
67+
- Breaking API changes
68+
69+
## Code Review Process
70+
71+
1. Maintainer will review your PR within a few days
72+
2. Address any feedback or requested changes
73+
3. Once approved, maintainer will merge
74+
75+
## License
76+
77+
By contributing, you agree that your contributions will be licensed under the MIT License.

README.md

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ A lightweight fluent IMAP client for PHP 8.1+. Decode bodies, attachments, and h
1515
5. [Field & Filter Reference](#field--filter-reference)
1616
6. [Demo Application](#demo-application)
1717
7. [Error Handling](#error-handling)
18-
8. [Development & Testing](#development--testing)
19-
9. [Troubleshooting](#troubleshooting)
20-
10. [License](#license)
18+
8. [Security](#security)
19+
9. [Development & Testing](#development--testing)
20+
10. [Contributing](#contributing)
21+
11. [Troubleshooting](#troubleshooting)
22+
12. [License](#license)
2123

2224
---
2325

@@ -317,6 +319,54 @@ try {
317319

318320
---
319321

322+
## Security
323+
324+
**Important:** Never hardcode IMAP credentials in your source code.
325+
326+
### Secure Credential Management
327+
328+
```php
329+
use Yai\Ymap\ImapService;
330+
331+
// ✓ Good: Use environment variables
332+
$messages = ImapService::create()
333+
->connect(
334+
getenv('IMAP_MAILBOX'),
335+
getenv('IMAP_USER'),
336+
getenv('IMAP_PASS')
337+
)
338+
->getMessages();
339+
340+
// ✗ Bad: Hardcoded credentials
341+
$messages = ImapService::create()
342+
->connect('{imap.gmail.com:993/imap/ssl}INBOX', '[email protected]', 'password')
343+
->getMessages();
344+
```
345+
346+
### Secure Connections
347+
348+
Always use SSL/TLS when connecting over untrusted networks:
349+
350+
```php
351+
// ✓ Good: SSL enabled
352+
'{imap.gmail.com:993/imap/ssl}INBOX'
353+
354+
// ⚠️ Warning: Disables certificate validation (development only)
355+
'{imap.example.com:993/imap/ssl/novalidate-cert}INBOX'
356+
```
357+
358+
### Additional Security Practices
359+
360+
- **Limit result sets** to prevent resource exhaustion (`->limit(100)`)
361+
- **Sanitize filenames** before saving attachments to disk
362+
- **Validate MIME types** when processing attachments
363+
- **Implement rate limiting** for web-facing IMAP operations
364+
- **Use field selection** to minimize data exposure (`->fields(['uid', 'subject'])`)
365+
366+
For detailed security guidelines, vulnerability reporting, and best practices, see [SECURITY.md](SECURITY.md).
367+
368+
---
369+
320370
## Development & Testing
321371

322372
```bash
@@ -329,6 +379,19 @@ No additional tooling is required. PHPStan level is configured in `phpstan.neon`
329379

330380
---
331381

382+
## Contributing
383+
384+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on:
385+
386+
- Code standards and style (PHP 8.1+, strict typing, PHPStan level 8)
387+
- Pull request process
388+
- What to contribute (bug fixes, docs, tests, performance improvements)
389+
- How to report issues
390+
391+
For security vulnerabilities, please see our [Security Policy](SECURITY.md) instead of opening a public issue.
392+
393+
---
394+
332395
## Troubleshooting
333396

334397
| Issue | Hint |

SECURITY.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Security Policy
2+
3+
## Supported Versions
4+
5+
| Version | Supported |
6+
| ------- | ------------------ |
7+
| 1.0.x | :white_check_mark: |
8+
| < 1.0 | :x: |
9+
10+
## Reporting a Vulnerability
11+
12+
If you discover a security vulnerability in php-ymap, please report it privately:
13+
14+
**DO NOT open a public GitHub issue for security vulnerabilities.**
15+
16+
### How to Report
17+
18+
1. **Email:** Send details to the maintainers via GitHub (use the "Report a security vulnerability" feature in the Security tab)
19+
2. **Include:**
20+
- Description of the vulnerability
21+
- Steps to reproduce
22+
- Potential impact
23+
- Suggested fix (if available)
24+
25+
### What to Expect
26+
27+
- **Acknowledgment:** Within 48 hours
28+
- **Initial assessment:** Within 1 week
29+
- **Fix timeline:** Depends on severity and complexity
30+
- **Credit:** You will be credited in the security advisory (unless you prefer to remain anonymous)
31+
32+
## Security Best Practices for Users
33+
34+
### Credential Management
35+
36+
**DO:**
37+
- Store IMAP credentials in environment variables
38+
- Use secure vaults (AWS Secrets Manager, HashiCorp Vault, etc.)
39+
- Rotate credentials regularly
40+
- Use application-specific passwords when available
41+
42+
**DON'T:**
43+
- Hardcode credentials in source code
44+
- Commit credentials to version control
45+
- Log credentials in plain text
46+
- Share credentials across multiple applications
47+
48+
### TLS/SSL Configuration
49+
50+
php-ymap connects to IMAP servers using the native PHP IMAP extension. Ensure secure connections:
51+
52+
```php
53+
$config = new ConnectionConfig(
54+
host: 'imap.example.com',
55+
port: 993, // Use SSL port
56+
username: getenv('IMAP_USER'),
57+
password: getenv('IMAP_PASS'),
58+
flags: '/imap/ssl', // Enable SSL
59+
mailbox: 'INBOX'
60+
);
61+
```
62+
63+
**Flags for secure connections:**
64+
- `/imap/ssl` - Use SSL/TLS encryption
65+
- `/imap/ssl/novalidate-cert` - **Avoid in production** (disables certificate verification)
66+
67+
### Input Validation
68+
69+
When using php-ymap in web applications:
70+
71+
- **Sanitize user inputs** before using in IMAP searches
72+
- **Validate email addresses** before using in filters
73+
- **Limit result sets** to prevent resource exhaustion
74+
- **Implement rate limiting** on IMAP operations
75+
76+
### Attachment Handling
77+
78+
When processing attachments:
79+
80+
```php
81+
// Sanitize filenames before saving to disk
82+
$filename = basename($attachment->getFilename());
83+
$filename = preg_replace('/[^a-zA-Z0-9._-]/', '_', $filename);
84+
85+
// Validate file types
86+
$allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
87+
if (!in_array($attachment->getContentType(), $allowedTypes)) {
88+
// Reject or handle appropriately
89+
}
90+
91+
// Limit file sizes
92+
if ($attachment->getSize() > 10 * 1024 * 1024) { // 10MB
93+
// Reject large attachments
94+
}
95+
```
96+
97+
### Resource Limits
98+
99+
Prevent memory exhaustion:
100+
101+
```php
102+
// Limit number of messages fetched
103+
$messages = $service
104+
->inbox()
105+
->limit(100) // Don't fetch unbounded result sets
106+
->fetch();
107+
108+
// Use field selection to reduce memory usage
109+
$messages = $service
110+
->inbox()
111+
->fields(['uid', 'subject', 'from', 'date']) // Omit large bodies
112+
->fetch();
113+
```
114+
115+
## Known Security Considerations
116+
117+
1. **IMAP Extension:** php-ymap depends on PHP's native IMAP extension which uses the c-client library. Keep PHP updated to receive security patches.
118+
119+
2. **Memory Usage:** Large attachments are loaded into memory. For production use with large attachments, consider implementing streaming (see TASK_LIST.md).
120+
121+
3. **Connection Security:** Always use SSL/TLS for IMAP connections when connecting over untrusted networks.
122+
123+
## Disclosure Policy
124+
125+
When a security issue is fixed:
126+
127+
1. A security advisory will be published on GitHub
128+
2. CHANGELOG.md will be updated with security fix details
129+
3. A new patch version will be released
130+
4. Affected versions will be clearly documented
131+
132+
## Security Updates
133+
134+
Subscribe to security advisories:
135+
- Watch the GitHub repository for security alerts
136+
- Check CHANGELOG.md for security-related fixes

composer.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"description": "Lightweight IMAP reader/flagger for PHP 8.1+",
44
"type": "library",
55
"license": "MIT",
6+
"minimum-stability": "stable",
67
"require": {
78
"php": "^8.1",
89
"ext-imap": "*",
@@ -18,5 +19,17 @@
1819
"psr-4": {
1920
"Yai\\Ymap\\": "src/"
2021
}
22+
},
23+
"scripts": {
24+
"test": "phpunit",
25+
"test:verbose": "phpunit --testdox",
26+
"test:coverage": "phpunit --coverage-html coverage/",
27+
"test:filter": "phpunit --filter",
28+
"analyse": "phpstan analyse src/",
29+
"analyse:baseline": "phpstan analyse src/ --generate-baseline",
30+
"check": [
31+
"@analyse",
32+
"@test"
33+
]
2134
}
2235
}

example/index.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1310,8 +1310,9 @@ class="action-btn"
13101310
This demo uses <strong>YEH</strong> (Yai Event Hub) - a lightweight event delegation library for modern web apps.
13111311
</p>
13121312
<ul style="color: var(--muted); padding-left: 1.25rem; margin-bottom: 0.75rem;">
1313-
<li><a href="https://yaijs.github.io/yai/docs/yeh/" target="_blank" rel="noopener" style="color: #a78bfa;">YEH Documentation</a> — Event delegation made simple</li>
1313+
<li><a href="https://jsfiddle.net/hb9t3gam/" target="_blank" rel="noopener" style="color: #a78bfa;">YEH on JSF</a> — YEH toggleTarget Examples on Jsfiddle</li>
13141314
<li><a href="https://yaijs.github.io/yai/tabs/Example.html" target="_blank" rel="noopener" style="color: #a78bfa;">YaiTabs Live Demo</a> — Advanced tab system built on YEH</li>
1315+
<li><a href="https://yaijs.github.io/yai/docs/yeh/" target="_blank" rel="noopener" style="color: #a78bfa;">YEH Documentation</a> — Event delegation made simple</li>
13151316
</ul>
13161317
<hr />
13171318
<pre>super({<br> '#app': ['click', 'input', 'submit'],<br> 'window': ['scroll']<br>})</pre>

phpunit.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
6+
failOnWarning="true"
7+
failOnRisky="true">
8+
<testsuites>
9+
<testsuite name="php-ymap Test Suite">
10+
<directory>tests</directory>
11+
</testsuite>
12+
</testsuites>
13+
<source>
14+
<include>
15+
<directory>src</directory>
16+
</include>
17+
</source>
18+
</phpunit>

src/ImapService.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ public function getMessages(array $overrides = []): array
289289
$uidsToFetch = array_slice($uids, 0, $config->limit);
290290

291291
$activeFields = $config->getActiveFields();
292+
$messages = [];
292293

293294
foreach ($uidsToFetch as $uid) {
294295
try {

src/ServiceConfig.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,12 @@ public static function fromArray(array $config): self
128128
$instance->since = $filters['since'] ?? null;
129129
$instance->before = $filters['before'] ?? null;
130130

131-
if (array_key_exists('unread', $filters)) {
132-
$instance->unread = $filters['unread'];
133-
}
134-
if (array_key_exists('answered', $filters)) {
135-
$instance->answered = $filters['answered'];
136-
}
131+
if (array_key_exists('unread', $filters)) {
132+
$instance->unread = $filters['unread'];
133+
}
134+
if (array_key_exists('answered', $filters)) {
135+
$instance->answered = $filters['answered'];
136+
}
137137

138138
$instance->from = $filters['from'] ?? null;
139139
$instance->to = $filters['to'] ?? null;

0 commit comments

Comments
 (0)