Coder Social home page Coder Social logo

phpgt / input Goto Github PK

View Code? Open in Web Editor NEW
3.0 3.0 3.0 434 KB

Encapsulated and type-safe user input.

Home Page: https://www.php.gt/input

License: MIT License

PHP 100.00%
user-input encapsulation security php-security querystring file-upload encryption stream

input's People

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

input's Issues

Multiple file upload support

More than one file can be uploaded in the same field. How should this be handled?

A single file is represented by a FileUpload currently. Maybe a MultiFileUpload object is the simple solution here.

Filter

Even though XSS isn't really a threat when using the DOM, most user input probably wants to be put through something like strip_tags. Someone's username of <script>alert("pwned")</script> would be nice to clean up.

Register a callback function to filter all user input, or just particular keys.

Idea:

$this->input->filter([$myClass, $inputFilter->filter(...)]) to pass EVERY kvp through your callback.
$this->input->filter([$myClass, $inputFilter->filter(...)], "name", "postcode") to only pass name and postcode kvps through your callback.

getFile is inconsistent

All other getters return a nullable type, depending on whether the field is present in the input.

getFile throws an exception if there isn't a file.

Make it return null instead, so it's consistent.

callWithParameters function

$this->input
	->with("dateRange", "source", "level", "regex")
	->call([$this, "setFilters"]);

The setFilters function will receive a single parameter of type CombinedInputData. It might be useful to use a function such as callWithParameters so the setFilters function is then called with individual parameters.

New method: withPrefix

Receives all input parameters that start with the given prefix.

$input->do("save")
	->withPrefix("io_")
	->call("processIo");

function processIo(InputData $data) {
	// $data["product_id"] // from input name=io_product_id
}
<form method="post">
	<input name="io_product_id" />
	<button name="do" value="save">Save</button>
</form>

Pass all input parameters by default when no with/without called

Nobody will ever want no input - missing with/without obviously means withAll()

Taking the example from the readme:

$payment = new PaymentProcessor();
$input->do("pay")
	->call([$payment, "processCard"]);

The $payment object's processCard function should receive an InputData object with all of the fields by default, as none have been specified with or without.

InputValueGetter should contain getFile, getDateTime

Consolidate getter functions into this trait.

There is a trait called InputValueGetter which allows the get* methods to be shared with the InputData and Input class itself. A couple of functions have not been maintained correctly and need moving into this trait so all areas of the code are consistent.

For discussion: should we return DateTime or DateTimeImmutable?

$dateFrom = $input->getDateTime("date-from");

What type of DateTime should this library return in this case? I like the simplicity of DateTime, and with it being a getter it probably doesn't matter about immutability of the object itself, but would it make more sense or would it potentially confuse someone if the returned object was DateTimeImmutable?

More flexible `when` syntax

Following on from #15, now it is possible to create a trigger with just the keys, it would be nice to allow variable arguments to the when function.

Examples:

$input->when("code"); // triggers when "code" is present
$input->when("code", "email"); // triggers when "code" and "email" are present
$input->when(["code" => "1234", "email"]); // triggers when "code" is "1234" and "email" is present

Better API design

UploadData should be included in the Input's internal data collection, so that it can be referred to in exactly the same way as query string or request body parameters.

The added benefit of this means that callbacks can be passed dynamic functions that can benefit from the type juggling of PHP.

To achieve this, all parameters (apart from extras passed in to ->call()) need to be InputDatum types. An InputDatum is a representation of a single request KVP.

$input->do("update-settings")
	->with("username", "password", "profile-photo")
	->call("saveSettings");

function saveSettings(string $username, string $password, FileUpload $profilePhoto) {
	// Dynamic properties with correct type, juggled by PHP.
}

Input Documentation

There is currently no documentation. This is the outline I'd like to cover:

  • Intro: PHP handles user input globally; HTTP has some shortcomings when dealing with multiple fields, especially multiple files; this repo is a consistent interface to dealing with HTTP user input.
  • Example: Show WebEngine example usage of a typical form submission.
  • Accessing the user input: WebEngine automatically instantiates the Input class - ready for use in the go/do functions; how to construct the Input class manually.
  • Trigger functions: executing a function "when" a user input key is present; "do" functions are automatically hooked up in WebEngine.
  • Working with files: one consistent interface for working with data or files; type safety with files and other data types; security considerations.

Call `do*` functions automatically

Provide a list of classes to check for do* methods, calling matching ones automatically.

If a do named parameter comes in, automatically call the matching callback.

For example, <button name="do" value="login">Login</button> should automatically call YourPage::doLogin , if it exists.

Streaming large files

Recently in a WebEngine project I've had to work with the uploading of seriously large files.

Allowing POST input larger than a gigabyte seems massively excessive, and has to be done on the php.ini level so can't be controlled per-script.

The solution was to use a PUT request, then fopen("php://input", "r") to deal with the incoming data.

Could this be handled automatically by Input? The FileInput object could expose a getStream method that works under POST and PUT requests, but with PUT the file wouldn't automatically be loaded into memory.

with("something-that-does-not-exist") - throw exception

When using with(), what should happen if a key is requested that doesn't actually exist on the request?

It sounds like an exceptional circumstance to me, so there should probably be a MissingInputKeyException or something like that thrown.

Add a test to InputTest.php after testWithExist called testWithNotExist

Implement File and FileList objects

The combined data array should also contain files (loaded originally from $_FILES).

$fileObject = $input->get("my-photo"); // Gt\Input\File

$input->with("name", "my-photo")->call(function(InputData $data) {
    echo $data["name"];
    $data["my-photo"]->move("/tmp"); // move_uploaded_file
});

Automatic security applied to forms

Imagine a user enters their credit card information into a form and presses submit. Unsetting the globals doesn't directly enhance security, as php://input is still available, and credit card information should not be able to be read at all by third party code.

Using openssl (or similar) to secure user input is the answer. Either on all forms, or opted in forms, security should be applied automatically.

It should work like this:

  • The public key of the application is injected as a hidden field on the form to be secured.
  • A plain text password is also injected into the form.
  • JavaScript should encrypt the data on submit, using the public key.
  • PHP.Gt/Input should throw an exception and halt execution if the plaintext password is visible in plain text on php://input - this means the JavaScript has not been run.

Post and get with the same key

There are sometimes occasions where a query string parameter is present, but it is then overwritten by a POST field of the same name. Test that the POST will always overwrite the value of the GET value.

Malicious/stupid inputs

There needs to be protection against stupid inputs that could be used to break things.

Values should be fine, because they should be able to accept anything.

What if input keys contained HTML or SQL?

Just an idea for now.

Improve test data

Tests are quite slow to run, because there is so much random data generated in the PHPUnit dataProviders.

Hundreds of random input strings are generated in the unit tests, there are two improvements I can see:

  • It would be a better test to see input of varying length (very short, and very long)
  • There doesn't need to be as much random data generated, so the tests run quicker

"or" trigger

Following on from #59, setting a callback for when a trigger is not fired is useful for terse code.

Example:

$this->input
->when("name", "age")
->call([$this, "setDetails"])
->or([$this, "setDefault"]);

Handle hyphens and underscores in do callbacks

do callbacks are called automatically now, but if the name of the input contains a hyphen, a corresponding function name can't be found.

Handle hyphen and underscore separated words by converting do => do-something to do => doSomething

Validation

We have some great functionality for accepting user input, but what about validating it?

API starting point ideas:

$rules = new ValidationRules()
	->required("username", "password", "dob")
	->minimum("password", 8)
	->type("dob", ValidationRules::FORMAT_DATE);

$this->input->do("save-profile")
	->validate($rules)
	->with("username", "password", "profile-picture")
	->call([$this, "saveProfile"])
	->invalidCall([$this, "displayError"]);

I have no solid ideas of how this function could work yet, but passing it a ValidationRules object sounds like a good starting point.

Provide mechanism for unsetting globals

This is currently done in WebEngine, and will need to be done in any code that uses this library, so provide a mechanism for this library to unset the globals automatically.

Trigger `when` key is present

Currently the when trigger mechanism requires to know the value.

$input->when(["something"]) can be used. Notice the non-associative array. This can be used to trigger when something is set to any value.

Deprecation notices in 8.1

Deprecated: Return type of Gt\Input\InputData\AbstractInputData::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/g105b/Code/Project/website/vendor/phpgt/input/src/InputData/KeyValueIterator.php on line 26

Deprecated: Return type of Gt\Input\InputData\AbstractInputData::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/g105b/Code/Project/website/vendor/phpgt/input/src/InputData/KeyValueIterator.php on line 22

Deprecated: Return type of Gt\Input\InputData\AbstractInputData::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/g105b/Code/Project/website/vendor/phpgt/input/src/InputData/KeyValueArrayAccess.php on line 45

Deprecated: Return type of Gt\Input\InputData\AbstractInputData::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/g105b/Code/Project/website/vendor/phpgt/input/src/InputData/KeyValueArrayAccess.php on line 13

Deprecated: Return type of Gt\Input\Input::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/g105b/Code/Project/website/vendor/phpgt/input/src/InputData/KeyValueIterator.php on line 26

Deprecated: Return type of Gt\Input\Input::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/g105b/Code/Project/website/vendor/phpgt/input/src/InputData/KeyValueIterator.php on line 22

Deprecated: Return type of Gt\Input\Input::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/g105b/Code/Project/website/vendor/phpgt/input/src/InputData/KeyValueArrayAccess.php on line 45

Deprecated: Return type of Gt\Input\Input::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/g105b/Code/Project/website/vendor/phpgt/input/src/InputData/KeyValueArrayAccess.php on line 13

getMutlipleFile returns InputDatum

Fatal error: Uncaught TypeError: Gt\Input\Input::getMultipleFile(): Return value must be of type Gt\Input\InputData\Datum\MultipleInputDatum, Gt\Input\InputData\Datum\InputDatum returned in /home/guy/insulmap/vendor/phpgt/input/src/InputValueGetter.php:73 Stack trace: #0 gt-logic-stream://page/project/@project/soft-cover/rfi/edit.php(188): Gt\Input\Input->getMultipleFile() #1 gt-logic-stream://page/project/@project/soft-cover/rfi/edit.php(93): InsulMap\Page\Project\Project\SoftCover\Rfi\EditPage->save() #2 [internal function]: InsulMap\Page\Project\Project\SoftCover\Rfi\EditPage->do_save_request() #3 /home/guy/insulmap/vendor/phpgt/servicecontainer/src/Injector.php(60): ReflectionMethod->invoke() #4 /home/guy/insulmap/vendor/phpgt/webengine/src/Logic/LogicExecutor.php(37): Gt\ServiceContainer\Injector->invoke() #5 /home/guy/insulmap/vendor/phpgt/webengine/src/Middleware/RequestHandler.php(221): Gt\WebEngine\Logic\LogicExecutor->invoke() #6 [internal function]: Gt\WebEngine\Middleware\RequestHandler->Gt\WebEngine\Middleware{closure}() #7 /home/guy/insulmap/vendor/phpgt/input/src/Trigger/Callback.php(25): call_user_func_array() #8 /home/guy/insulmap/vendor/phpgt/input/src/Trigger/Trigger.php(149): Gt\Input\Trigger\Callback->call() #9 /home/guy/insulmap/vendor/phpgt/input/src/Trigger/Trigger.php(134): Gt\Input\Trigger\Trigger->callCallbacks() #10 /home/guy/insulmap/vendor/phpgt/input/src/Trigger/Trigger.php(93): Gt\Input\Trigger\Trigger->fire() #11 /home/guy/insulmap/vendor/phpgt/webengine/src/Middleware/RequestHandler.php(223): Gt\Input\Trigger\Trigger->call() #12 /home/guy/insulmap/vendor/phpgt/webengine/src/Middleware/Lifecycle.php(91): Gt\WebEngine\Middleware\RequestHandler->handle() #13 /home/guy/insulmap/vendor/phpgt/webengine/src/Middleware/Lifecycle.php(67): Gt\WebEngine\Middleware\Lifecycle->process() #14 /home/guy/insulmap/vendor/phpgt/webengine/go.php(48): Gt\WebEngine\Middleware\Lifecycle->start() #15 {main} thrown in /home/guy/insulmap/vendor/phpgt/input/src/InputValueGetter.php on line 73

Check empty value

$input->has("something") returns true even if "something" value is an empty string.

Improvement for when checking if there is a meaningful value: $input->hasNonEmpty("something");

InputCallback

I'm fleshing out the syntax for a more encapsulated user input model. Taking the "do" terminology from Gt v2, as it is short, fun and friendly.

This interaction model enforces encapsulation, meaning the developer writing the go function must decide which areas of code receive user input, making it impossible for third party libraries to eavesdrop.

Hello, you:

<h1>Hello, <span id="output">you</span>!</h1>

<form>
	<input name="name" required placeholder="Your name" />
	<button name="do" value="submit">Submit!</button>
</form>
public function go() {
// Somewhere to output the variable:
	$outputTo = $this->document->getElementById("output");

// Trigger when "do=submit" is passed.
	$this->input->do("submit")
// List the parameters to pass to the callback.
		->with("name")
// Reference the callback, with optional added properties
		->call([$this, "outputName"], $outputTo);

protected function outputName(InputFields $fields, Node $outputTo) {
//	...
}

Credit card payment:

<form method="post">
	<input name="name" required />
	<input name="postcode" required />
	<input name="creditcard" required />
	<button name="do" value="pay">Pay me your money</button>
</form>
public function go() {
	$this->input->do("pay")
// All fields need passing to payment processor:
		->withAll()
		->call([Payment::class, "process"])
// ...but the user storage should not see credit card information.
		->without("creditcard")
		->call([User::class, "storeDetails"]);
}

A short syntax for developers who don't need extra features:

public function go() {
	$this->input->do("something")->callWithAll(function(InputFields $fields) {
		foreach($fields as $field) {
			someAction($field);
		}

		somethingElse($fields->fieldName);
	});
}

Null values if empty input

For all typed InputGetter functions apart from getString, if the input is an empty string, the result should be null.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.