add: adding cookie package
This commit is contained in:
parent
40168bfe8d
commit
566fd273ab
12 changed files with 490 additions and 0 deletions
7
packages/cookie/.gitignore
vendored
Normal file
7
packages/cookie/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
# https://dart.dev/guides/libraries/private-files
|
||||
# Created by `dart pub`
|
||||
.dart_tool/
|
||||
|
||||
# Avoid committing pubspec.lock for library packages; see
|
||||
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
pubspec.lock
|
3
packages/cookie/CHANGELOG.md
Normal file
3
packages/cookie/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## 1.0.0
|
||||
|
||||
- Initial version.
|
10
packages/cookie/LICENSE.md
Normal file
10
packages/cookie/LICENSE.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
The Laravel Framework is Copyright (c) Taylor Otwell
|
||||
The Fabric Framework is Copyright (c) Vieo, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
101
packages/cookie/README.md
Normal file
101
packages/cookie/README.md
Normal file
|
@ -0,0 +1,101 @@
|
|||
# Platform Cookie
|
||||
|
||||
A Dart implementation of Laravel-inspired cookie management for the Protevus platform.
|
||||
|
||||
## Features
|
||||
|
||||
- Create and manage cookies with various options (domain, path, secure, httpOnly, sameSite, etc.)
|
||||
- Queue and unqueue cookies
|
||||
- Handle cookie value prefixes
|
||||
- Proper encoding of cookie values
|
||||
- Expiration time handling
|
||||
|
||||
## Installation
|
||||
|
||||
Add this package to your `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
platform_cookie: ^1.0.0
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```
|
||||
dart pub get
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Here's a basic example of how to use the `CookieJar` class:
|
||||
|
||||
```dart
|
||||
import 'package:platform_cookie/platform_cookie.dart';
|
||||
|
||||
void main() {
|
||||
final cookieJar = CookieJar(
|
||||
domain: 'example.com',
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'Lax',
|
||||
);
|
||||
|
||||
// Create a simple cookie
|
||||
final simpleCookie = cookieJar.make('simple_cookie', 'Hello, Protevus!');
|
||||
print('Simple Cookie: $simpleCookie');
|
||||
|
||||
// Create a cookie with custom options
|
||||
final customCookie = cookieJar.make(
|
||||
'custom_cookie',
|
||||
'Custom Value',
|
||||
path: '/admin',
|
||||
minutes: 60,
|
||||
httpOnly: false,
|
||||
);
|
||||
print('Custom Cookie: $customCookie');
|
||||
|
||||
// Queue a cookie
|
||||
cookieJar.queue('queued_cookie', 'Queued Value', {'minutes': 30});
|
||||
print('Has Queued Cookie: ${cookieJar.hasQueued('queued_cookie')}');
|
||||
|
||||
// Get all queued cookies
|
||||
final queuedCookies = cookieJar.getQueuedCookies();
|
||||
print('Queued Cookies: $queuedCookies');
|
||||
|
||||
// Unqueue a cookie
|
||||
cookieJar.unqueue('queued_cookie');
|
||||
print('Has Queued Cookie after unqueue: ${cookieJar.hasQueued('queued_cookie')}');
|
||||
|
||||
// Using CookieValuePrefix
|
||||
final cookieName = 'prefixed_cookie';
|
||||
final cookieValue = 'Prefixed Value';
|
||||
final key = 'secret_key';
|
||||
|
||||
final prefix = CookieValuePrefix.create(cookieName, key);
|
||||
final prefixedValue = '$prefix$cookieValue';
|
||||
|
||||
print('Prefixed Cookie Value: $prefixedValue');
|
||||
|
||||
// Validating and removing prefix
|
||||
final validatedValue = CookieValuePrefix.validate(cookieName, prefixedValue, [key]);
|
||||
print('Validated Cookie Value: $validatedValue');
|
||||
}
|
||||
```
|
||||
|
||||
For more detailed examples, check the `example` folder in the package repository.
|
||||
|
||||
## Testing
|
||||
|
||||
To run the tests for this package, use the following command:
|
||||
|
||||
```
|
||||
dart test
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read our contributing guidelines before submitting pull requests.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License.
|
30
packages/cookie/analysis_options.yaml
Normal file
30
packages/cookie/analysis_options.yaml
Normal file
|
@ -0,0 +1,30 @@
|
|||
# This file configures the static analysis results for your project (errors,
|
||||
# warnings, and lints).
|
||||
#
|
||||
# This enables the 'recommended' set of lints from `package:lints`.
|
||||
# This set helps identify many issues that may lead to problems when running
|
||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||
# style and format.
|
||||
#
|
||||
# If you want a smaller set of lints you can change this to specify
|
||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||
# (the recommended set includes the core lints).
|
||||
# The core lints are also what is used by pub.dev for scoring packages.
|
||||
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
# Uncomment the following section to specify additional rules.
|
||||
|
||||
# linter:
|
||||
# rules:
|
||||
# - camel_case_types
|
||||
|
||||
# analyzer:
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
||||
|
||||
# For more information about the core and recommended set of lints, see
|
||||
# https://dart.dev/go/core-lints
|
||||
|
||||
# For additional information about configuring this file, see
|
||||
# https://dart.dev/guides/language/analysis-options
|
63
packages/cookie/example/cookie_example.dart
Normal file
63
packages/cookie/example/cookie_example.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
import 'package:platform_cookie/platform_cookie.dart';
|
||||
|
||||
void main() {
|
||||
// Create a CookieJar instance
|
||||
final cookieJar = CookieJar(
|
||||
domain: 'example.com',
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'Lax',
|
||||
);
|
||||
|
||||
// Example 1: Create a simple cookie
|
||||
final simpleCookie = cookieJar.make('simple_cookie', 'Hello, Protevus!');
|
||||
print('Simple Cookie: $simpleCookie');
|
||||
|
||||
// Example 2: Create a cookie with custom options
|
||||
final customCookie = cookieJar.make(
|
||||
'custom_cookie',
|
||||
'Custom Value',
|
||||
path: '/admin',
|
||||
minutes: 60,
|
||||
httpOnly: false,
|
||||
);
|
||||
print('Custom Cookie: $customCookie');
|
||||
|
||||
// Example 3: Queue a cookie
|
||||
cookieJar.queue('queued_cookie', 'Queued Value', {'minutes': 30});
|
||||
print('Has Queued Cookie: ${cookieJar.hasQueued('queued_cookie')}');
|
||||
|
||||
// Example 4: Get all queued cookies
|
||||
final queuedCookies = cookieJar.getQueuedCookies();
|
||||
print('Queued Cookies: $queuedCookies');
|
||||
|
||||
// Example 5: Unqueue a cookie
|
||||
cookieJar.unqueue('queued_cookie');
|
||||
print(
|
||||
'Has Queued Cookie after unqueue: ${cookieJar.hasQueued('queued_cookie')}');
|
||||
|
||||
// Example 6: Using CookieValuePrefix
|
||||
final cookieName = 'prefixed_cookie';
|
||||
final cookieValue = 'Prefixed Value';
|
||||
final key = 'secret_key';
|
||||
|
||||
final prefix = CookieValuePrefix.create(cookieName, key);
|
||||
final prefixedValue = '$prefix$cookieValue';
|
||||
|
||||
print('Prefixed Cookie Value: $prefixedValue');
|
||||
|
||||
// Example 7: Validating and removing prefix
|
||||
final validatedValue =
|
||||
CookieValuePrefix.validate(cookieName, prefixedValue, [key]);
|
||||
print('Validated Cookie Value: $validatedValue');
|
||||
|
||||
// Example 8: Creating a cookie with raw value
|
||||
final rawCookie =
|
||||
cookieJar.make('raw_cookie', 'Raw Value with spaces', raw: true);
|
||||
print('Raw Cookie: $rawCookie');
|
||||
|
||||
// Example 9: Creating a cookie with encoded value
|
||||
final encodedCookie =
|
||||
cookieJar.make('encoded_cookie', 'Encoded Value with spaces');
|
||||
print('Encoded Cookie: $encodedCookie');
|
||||
}
|
4
packages/cookie/lib/platform_cookie.dart
Normal file
4
packages/cookie/lib/platform_cookie.dart
Normal file
|
@ -0,0 +1,4 @@
|
|||
library platform_cookie;
|
||||
|
||||
export 'src/cookie_jar.dart';
|
||||
export 'src/cookie_value_prefix.dart';
|
100
packages/cookie/lib/src/cookie_jar.dart
Normal file
100
packages/cookie/lib/src/cookie_jar.dart
Normal file
|
@ -0,0 +1,100 @@
|
|||
import 'package:platform_contracts/contracts.dart';
|
||||
import 'package:platform_macroable/platform_macroable.dart';
|
||||
import 'package:platform_support/platform_support.dart';
|
||||
|
||||
class CookieJar
|
||||
with Macroable, InteractsWithTime
|
||||
implements QueueingFactory, CookieFactory {
|
||||
final Map<String, Map<String, dynamic>> _queued = {};
|
||||
final String _path;
|
||||
final String _domain;
|
||||
final bool _secure;
|
||||
final bool _httpOnly;
|
||||
final String? _sameSite;
|
||||
|
||||
CookieJar({
|
||||
String path = '/',
|
||||
String? domain,
|
||||
bool secure = false,
|
||||
bool httpOnly = true,
|
||||
String? sameSite,
|
||||
}) : _path = path,
|
||||
_domain = domain ?? '',
|
||||
_secure = secure,
|
||||
_httpOnly = httpOnly,
|
||||
_sameSite = sameSite;
|
||||
|
||||
@override
|
||||
Map<String, String> make(
|
||||
String name,
|
||||
String value, {
|
||||
String? domain,
|
||||
bool? httpOnly,
|
||||
int? minutes,
|
||||
String? path,
|
||||
bool? raw,
|
||||
String? sameSite,
|
||||
bool? secure,
|
||||
}) {
|
||||
final cookie = <String, String>{
|
||||
'name': name,
|
||||
'value': _encodeValue(value, raw ?? false),
|
||||
'domain': domain ?? _domain,
|
||||
'path': path ?? _path,
|
||||
'expires': _getExpirationTime(minutes),
|
||||
'secure': (secure ?? _secure).toString(),
|
||||
'httponly': (httpOnly ?? _httpOnly).toString(),
|
||||
'samesite': sameSite ?? _sameSite ?? '',
|
||||
};
|
||||
|
||||
return Map.fromEntries(
|
||||
cookie.entries.where((entry) => entry.value.isNotEmpty));
|
||||
}
|
||||
|
||||
@override
|
||||
void queue(String name, String value, [Map<String, dynamic>? options]) {
|
||||
_queued[name] = {'value': value, ...?options};
|
||||
}
|
||||
|
||||
bool hasQueued(String key) {
|
||||
return _queued.containsKey(key);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> getQueuedCookies() {
|
||||
return Map.fromEntries(_queued.entries.map((entry) => MapEntry(
|
||||
entry.key,
|
||||
make(
|
||||
entry.key,
|
||||
entry.value['value'] as String,
|
||||
domain: entry.value['domain'] as String?,
|
||||
httpOnly: entry.value['httpOnly'] as bool?,
|
||||
minutes: entry.value['minutes'] as int?,
|
||||
path: entry.value['path'] as String?,
|
||||
raw: entry.value['raw'] as bool?,
|
||||
sameSite: entry.value['sameSite'] as String?,
|
||||
secure: entry.value['secure'] as bool?,
|
||||
))));
|
||||
}
|
||||
|
||||
@override
|
||||
void unqueue(String name) {
|
||||
_queued.remove(name);
|
||||
}
|
||||
|
||||
String _encodeValue(String value, bool raw) {
|
||||
return raw ? value : Uri.encodeComponent(value);
|
||||
}
|
||||
|
||||
String _getExpirationTime(int? minutes) {
|
||||
if (minutes != null) {
|
||||
return currentTime()
|
||||
.add(Duration(minutes: minutes))
|
||||
.toUtc()
|
||||
.toIso8601String();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Additional methods can be added here as needed
|
||||
}
|
34
packages/cookie/lib/src/cookie_value_prefix.dart
Normal file
34
packages/cookie/lib/src/cookie_value_prefix.dart
Normal file
|
@ -0,0 +1,34 @@
|
|||
import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
class CookieValuePrefix {
|
||||
/// Create a new cookie value prefix for the given cookie name.
|
||||
static String create(String cookieName, String key) {
|
||||
final hmac = Hmac(sha1, utf8.encode(key));
|
||||
final digest = hmac.convert(utf8.encode('${cookieName}v2'));
|
||||
return '${digest.toString()}|';
|
||||
}
|
||||
|
||||
/// Remove the cookie value prefix.
|
||||
static String remove(String cookieValue) {
|
||||
final separatorIndex = cookieValue.indexOf('|');
|
||||
if (separatorIndex == -1 || separatorIndex == cookieValue.length - 1) {
|
||||
return cookieValue; // Return original value if no separator or separator at the end
|
||||
}
|
||||
return cookieValue.substring(separatorIndex + 1);
|
||||
}
|
||||
|
||||
/// Validate a cookie value contains a valid prefix.
|
||||
/// If it does, return the cookie value with the prefix removed.
|
||||
/// Otherwise, return null.
|
||||
static String? validate(
|
||||
String cookieName, String cookieValue, List<String> keys) {
|
||||
for (final key in keys) {
|
||||
final prefix = create(cookieName, key);
|
||||
if (cookieValue.startsWith(prefix)) {
|
||||
return remove(cookieValue);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
18
packages/cookie/pubspec.yaml
Normal file
18
packages/cookie/pubspec.yaml
Normal file
|
@ -0,0 +1,18 @@
|
|||
name: platform_cookie
|
||||
description: A Dart implementation of Laravel-inspired cookie management for the Protevus platform.
|
||||
version: 1.0.0
|
||||
homepage: https://protevus.com
|
||||
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
crypto: ^3.0.1
|
||||
platform_collections: ^8.0.0
|
||||
platform_contracts: ^8.0.0
|
||||
platform_macroable: ^8.0.0
|
||||
platform_support: ^8.0.0
|
||||
|
||||
dev_dependencies:
|
||||
test: ^1.16.0
|
||||
lints: ^2.0.0
|
72
packages/cookie/test/cookie_jar_test.dart
Normal file
72
packages/cookie/test/cookie_jar_test.dart
Normal file
|
@ -0,0 +1,72 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_cookie/src/cookie_jar.dart';
|
||||
|
||||
void main() {
|
||||
group('CookieJar', () {
|
||||
late CookieJar cookieJar;
|
||||
|
||||
setUp(() {
|
||||
cookieJar = CookieJar();
|
||||
});
|
||||
|
||||
test('make creates a cookie with default values', () {
|
||||
final cookie = cookieJar.make('name', 'value');
|
||||
expect(cookie['name'], equals('name'));
|
||||
expect(cookie['value'], equals('value'));
|
||||
expect(cookie['path'], equals('/'));
|
||||
expect(cookie['secure'], equals('false'));
|
||||
expect(cookie['httponly'], equals('true'));
|
||||
});
|
||||
|
||||
test('make creates a cookie with custom values', () {
|
||||
final cookie = cookieJar.make('name', 'value',
|
||||
domain: 'example.com',
|
||||
path: '/custom',
|
||||
secure: true,
|
||||
httpOnly: false,
|
||||
sameSite: 'Strict');
|
||||
expect(cookie['name'], equals('name'));
|
||||
expect(cookie['value'], equals('value'));
|
||||
expect(cookie['domain'], equals('example.com'));
|
||||
expect(cookie['path'], equals('/custom'));
|
||||
expect(cookie['secure'], equals('true'));
|
||||
expect(cookie['httponly'], equals('false'));
|
||||
expect(cookie['samesite'], equals('Strict'));
|
||||
});
|
||||
|
||||
test('queue adds a cookie to the queue', () {
|
||||
cookieJar.queue('name', 'value');
|
||||
expect(cookieJar.hasQueued('name'), isTrue);
|
||||
});
|
||||
|
||||
test('unqueue removes a cookie from the queue', () {
|
||||
cookieJar.queue('name', 'value');
|
||||
cookieJar.unqueue('name');
|
||||
expect(cookieJar.hasQueued('name'), isFalse);
|
||||
});
|
||||
|
||||
test('getQueuedCookies returns all queued cookies', () {
|
||||
cookieJar.queue('name1', 'value1');
|
||||
cookieJar.queue('name2', 'value2');
|
||||
final queuedCookies = cookieJar.getQueuedCookies();
|
||||
expect(queuedCookies.length, equals(2));
|
||||
expect(queuedCookies['name1']?['value'], equals('value1'));
|
||||
expect(queuedCookies['name2']?['value'], equals('value2'));
|
||||
});
|
||||
|
||||
test('make encodes value when raw is false', () {
|
||||
final cookie = cookieJar.make('name', 'value with spaces');
|
||||
expect(cookie['value'], equals('value%20with%20spaces'));
|
||||
});
|
||||
|
||||
test('make does not encode value when raw is true', () {
|
||||
final cookie = cookieJar.make('name', 'value with spaces', raw: true);
|
||||
expect(cookie['value'], equals('value with spaces'));
|
||||
});
|
||||
|
||||
test('make sets expiration time when minutes are provided', () {
|
||||
final cookie = cookieJar.make('name', 'value', minutes: 30);
|
||||
expect(cookie['expires'], isNotEmpty);
|
||||
});
|
||||
});
|
||||
}
|
48
packages/cookie/test/cookie_value_prefix_test.dart
Normal file
48
packages/cookie/test/cookie_value_prefix_test.dart
Normal file
|
@ -0,0 +1,48 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:platform_cookie/src/cookie_value_prefix.dart';
|
||||
|
||||
void main() {
|
||||
group('CookieValuePrefix', () {
|
||||
test('create returns a valid prefix', () {
|
||||
final prefix = CookieValuePrefix.create('cookieName', 'secret');
|
||||
expect(prefix, hasLength(41));
|
||||
expect(prefix, endsWith('|'));
|
||||
});
|
||||
|
||||
test('remove strips the prefix', () {
|
||||
const value = 'prefixValue|actualValue';
|
||||
expect(CookieValuePrefix.remove(value), equals('actualValue'));
|
||||
});
|
||||
|
||||
test('validate returns the value without prefix for a valid cookie', () {
|
||||
const cookieName = 'testCookie';
|
||||
const key = 'secret';
|
||||
final prefix = CookieValuePrefix.create(cookieName, key);
|
||||
final cookieValue = '${prefix}actualValue';
|
||||
|
||||
final result = CookieValuePrefix.validate(cookieName, cookieValue, [key]);
|
||||
expect(result, equals('actualValue'));
|
||||
});
|
||||
|
||||
test('validate returns null for an invalid cookie', () {
|
||||
const cookieName = 'testCookie';
|
||||
const key = 'secret';
|
||||
const cookieValue = 'invalidPrefix|actualValue';
|
||||
|
||||
final result = CookieValuePrefix.validate(cookieName, cookieValue, [key]);
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('validate checks multiple keys', () {
|
||||
const cookieName = 'testCookie';
|
||||
const key1 = 'secret1';
|
||||
const key2 = 'secret2';
|
||||
final prefix = CookieValuePrefix.create(cookieName, key2);
|
||||
final cookieValue = '${prefix}actualValue';
|
||||
|
||||
final result =
|
||||
CookieValuePrefix.validate(cookieName, cookieValue, [key1, key2]);
|
||||
expect(result, equals('actualValue'));
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue