Skip to content

Create a central class for parsing of AJAX parameters in POST/PUT #518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
[#512](https://github.com./nextcloud/cookbook/pull/512/) @seyfeb
- Speed up index of recipes by using computed properties
[#513](https://github.com./nextcloud/cookbook/pull/513) @christianlupus
- Central parsing of parameters for POST/PUT requests to simplify development
[#518](https://github.com./nextcloud/cookbook/pull/518) @christianlupus

### Fixed
- Fixed keywords of shared recipes counted multiple times, fixes #491
Expand Down
23 changes: 16 additions & 7 deletions lib/Controller/ConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use OCA\Cookbook\Service\RecipeService;
use OCP\IURLGenerator;
use OCA\Cookbook\Service\DbCacheService;
use OCA\Cookbook\Helper\RestParameterParser;

class ConfigController extends Controller {
/**
Expand All @@ -25,13 +26,19 @@ class ConfigController extends Controller {
* @var DbCacheService
*/
private $dbCacheService;

/**
* @var RestParameterParser
*/
private $restParser;

public function __construct($AppName, IRequest $request, IURLGenerator $urlGenerator, RecipeService $recipeService, DbCacheService $dbCacheService) {
public function __construct($AppName, IRequest $request, IURLGenerator $urlGenerator, RecipeService $recipeService, DbCacheService $dbCacheService, RestParameterParser $restParser) {
parent::__construct($AppName, $request);

$this->service = $recipeService;
$this->urlGenerator = $urlGenerator;
$this->dbCacheService = $dbCacheService;
$this->restParser = $restParser;
}

/**
Expand All @@ -55,17 +62,19 @@ public function list() {
public function config() {
$this->dbCacheService->triggerCheck();

if (isset($_POST['folder'])) {
$this->service->setUserFolderPath($_POST['folder']);
$data = $this->restParser->getParameters();

if (isset($data['folder'])) {
$this->service->setUserFolderPath($data['folder']);
$this->dbCacheService->updateCache();
}

if (isset($_POST['update_interval'])) {
$this->service->setSearchIndexUpdateInterval($_POST['update_interval']);
if (isset($data['update_interval'])) {
$this->service->setSearchIndexUpdateInterval($data['update_interval']);
}

if (isset($_POST['print_image'])) {
$this->service->setPrintImage((bool)$_POST['print_image']);
if (isset($data['print_image'])) {
$this->service->setPrintImage((bool)$data['print_image']);
}

return new DataResponse('OK', Http::STATUS_OK);
Expand Down
21 changes: 14 additions & 7 deletions lib/Controller/MainController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use OCP\AppFramework\Controller;
use OCA\Cookbook\Service\RecipeService;
use OCA\Cookbook\Service\DbCacheService;
use OCA\Cookbook\Helper\RestParameterParser;

class MainController extends Controller {
protected $appName;
Expand All @@ -26,14 +27,20 @@ class MainController extends Controller {
* @var IURLGenerator
*/
private $urlGenerator;

/**
* @var RestParameterParser
*/
private $restParser;

public function __construct(string $AppName, IRequest $request, RecipeService $recipeService, DbCacheService $dbCacheService, IURLGenerator $urlGenerator) {
public function __construct(string $AppName, IRequest $request, RecipeService $recipeService, DbCacheService $dbCacheService, IURLGenerator $urlGenerator, RestParameterParser $restParser) {
parent::__construct($AppName, $request);

$this->service = $recipeService;
$this->urlGenerator = $urlGenerator;
$this->appName = $AppName;
$this->dbCacheService = $dbCacheService;
$this->restParser = $restParser;
}

/**
Expand Down Expand Up @@ -300,12 +307,14 @@ public function create() {
public function import() {
$this->dbCacheService->triggerCheck();

if (!isset($_POST['url'])) {
$data = $this->restParser->getParameters();

if (!isset($data['url'])) {
return new DataResponse('Field "url" is required', 400);
}

try {
$recipe_file = $this->service->downloadRecipe($_POST['url']);
$recipe_file = $this->service->downloadRecipe($data['url']);
$recipe_json = $this->service->parseRecipeFile($recipe_file);
$this->dbCacheService->addRecipe($recipe_file);

Expand All @@ -323,7 +332,7 @@ public function new() {
$this->dbCacheService->triggerCheck();

try {
$recipe_data = $_POST;
$recipe_data = $this->restParser->getParameters();
$file = $this->service->addRecipe($recipe_data);
$this->dbCacheService->addRecipe($file);

Expand Down Expand Up @@ -370,9 +379,7 @@ public function update($id) {
$this->dbCacheService->triggerCheck();

try {
$recipe_data = [];

parse_str(file_get_contents("php://input"), $recipe_data);
$recipe_data = $this->restParser->getParameters();

$recipe_data['id'] = $id;

Expand Down
14 changes: 10 additions & 4 deletions lib/Controller/RecipeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use OCA\Cookbook\Service\RecipeService;
use OCP\IURLGenerator;
use OCA\Cookbook\Service\DbCacheService;
use OCA\Cookbook\Helper\RestParameterParser;

class RecipeController extends Controller {
/**
Expand All @@ -27,13 +28,19 @@ class RecipeController extends Controller {
* @var DbCacheService
*/
private $dbCacheService;

/**
* @var RestParameterParser
*/
private $restParser;

public function __construct($AppName, IRequest $request, IURLGenerator $urlGenerator, RecipeService $recipeService, DbCacheService $dbCacheService) {
public function __construct($AppName, IRequest $request, IURLGenerator $urlGenerator, RecipeService $recipeService, DbCacheService $dbCacheService, RestParameterParser $restParser) {
parent::__construct($AppName, $request);

$this->service = $recipeService;
$this->urlGenerator = $urlGenerator;
$this->dbCacheService = $dbCacheService;
$this->restParser = $restParser;
}

/**
Expand Down Expand Up @@ -89,8 +96,7 @@ public function show($id) {
public function update($id) {
$this->dbCacheService->triggerCheck();

$recipeData = [];
parse_str(file_get_contents("php://input"), $recipeData);
$recipeData = $this->restParser->getParameters();
$file = $this->service->addRecipe($recipeData);
$this->dbCacheService->addRecipe($file);

Expand All @@ -110,7 +116,7 @@ public function update($id) {
public function create() {
$this->dbCacheService->triggerCheck();

$recipeData = $_POST;
$recipeData = $this->restParser->getParameters();
$file = $this->service->addRecipe($recipeData);
$this->dbCacheService->addRecipe($file);

Expand Down
149 changes: 149 additions & 0 deletions lib/Helper/RestParameterParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php

namespace OCA\Cookbook\Helper;

use function GuzzleHttp\json_decode;
use OCP\IL10N;

class RestParameterParser {
private const CHARSET = 'charset';
private const CONTENT_TYPE = 'CONTENT_TYPE';
private const REQUEST_METHOD = 'REQUEST_METHOD';

/**
* @var IL10N
*/
private $l;

public function __construct(IL10N $l10n) {
$this->l = $l10n;
}

/**
* Fetch the parameters from the input accordingly
*
* Depending on the way of transmitting parameters from the browser to the PHP script different ways to recover these have to be done.
* This is a helper that should cover this.
*
* @return array The parameters transmitted
*/
public function getParameters(): array {
if (isset($_SERVER[self::CONTENT_TYPE])) {
$parts = explode(';', $_SERVER[self::CONTENT_TYPE], 2);

switch (trim($parts[0])) {
case 'application/json':
$enc = $this->getEncoding($_SERVER[self::CONTENT_TYPE]);
return $this->parseApplicationJSON($enc);
break;

case 'multipart/form-data':
if ($this->isPost()) {
return $_POST;
} else {
throw new \Exception($this->l->t('Cannot parse non-POST multipart encoding. This is a bug.'));
}
break;

case 'application/x-www-form-urlencoded':
if ($this->isPost()) {
return $_POST;
} else {
$enc = $this->getEncoding($_SERVER[self::CONTENT_TYPE]);
return $this->parseUrlEncoded($enc);
}
break;
}
} else {
throw new \Exception($this->l->t('Cannot detect type of transmitted data. This is a bug, please report it.'));
}
}

/**
* Parse data transmitted as application/json
* @param $encoding string The encoding to use
* @return array
*/
private function parseApplicationJSON(string $encoding): array {
$rawData = file_get_contents('php://input');

if ($encoding !== 'UTF-8') {
$rawData = iconv($encoding, 'UTF-8', $rawData);
}
return json_decode($rawData, true);
}

/**
* Parse the URL encoded value transmitted
*
* This is by far no ideal solution but just a quick hack in order to cope with this situation.
* Either use the POST method (where PHP does the parsing) or use application/json
*
* @param string $encoding The encoding to use
* @throws \Exception If the requested string is not well-formatted accoring to the minimal implementation
* @return array The values transmitted
*/
private function parseUrlEncoded(string $encoding): array {
$rawData = file_get_contents('php://input');
if ($encoding !== 'UTF-8') {
$rawData = iconv($encoding, 'UTF-8', $rawData);
}

$ret = [];
foreach (explode('&', $rawData) as $assignment) {
$parts = explode('=', $assignment, 2);

if (count($parts) < 2) {
throw new \Exception($this->l->t('Invlaid URL-encoded string found. Please report a bug.'));
}

$key = $parts[0];
$value = urldecode($parts[1]);

if (str_ends_with($key, '[]')) {
// Drop '[]' at the end
$key = substr($key, 0, -2);

if (!array_key_exists($key, $ret)) {
$ret[$key] = [];
}

$ret[$key][] = $value;
} else {
$ret[$key] = $value;
}
}

return $ret;
}

/**
* Get the encoding from the header
* @param string $header The header to parse
* @return string The encoding as string
*/
private function getEncoding(string $header): string {
$parts = explode(';', $header);

// Fallback encoding
$enc = 'UTF-8';

for ($i = 1; $i < count($parts); $i++) {
if (str_starts_with(trim($parts[$i]), self::CHARSET)) {
// Drop string 'charset='
$enc = substr(trim($parts[1]), strlen(self::CHARSET) + 1);
break;
}
}

return $enc;
}

/**
* Check if the request is a POST request
* @return bool true, if the request is a POST request.
*/
private function isPost(): bool {
return $_SERVER[self::REQUEST_METHOD] === 'POST';
}
}