Comment vérifier que la demande de POST est incomplète en PHP
je suis confronté à un problème quand un client web distant avec une connexion lente ne parvient pas à envoyer la demande de poste complète avec le contenu multipart/form-data
mais PHP utilise toujours des données reçues partiellement pour peupler le tableau $_POST
. En conséquence, une valeur dans le tableau $_POST
peut être incomplète et d'autres valeurs peuvent être manquantes. J'ai essayé de poser la même question dans Apache list first and got an an an an answer Qu'Apache ne cache pas le corps de la requête et passe il au module PHP comme un
le géant de blob.
Voici mon exemple de demande de courrier:
POST /test.php HTTP/1.0
Connection: close
Content-Length: 10000
Content-Type: multipart/form-data; boundary=ABCDEF
--ABCDEF
Content-Disposition: form-data; name="a"
A
--ABCDEF
vous pouvez voir que Content-Length
est 10000
octets, mais je n'envoie qu'un var a=A
.
le script PHP est:
<?php print_r($_REQUEST); ?>
serveur Web attend pendant environ 10 secondes pour le reste de ma demande (mais je n'envoie rien) et retourne ensuite cette réponse:
HTTP/1.1 200 OK
Date: Wed, 27 Nov 2013 19:42:20 GMT
Server: Apache/2.2.22 (Debian)
X-Powered-By: PHP/5.4.4-14+deb7u3
Vary: Accept-Encoding
Content-Length: 23
Connection: close
Content-Type: text/html
Array
(
[a] => A
)
So voici ma question: Comment puis-je vérifier en PHP que la demande post a été reçue complètement? $_SERVER['CONTENT_LENGTH']
afficherait 10000
à partir de l'en-tête de la requête, mais y a-t-il un moyen de vérifier la longueur réelle du contenu reçu?
13 réponses
je suppose que le client distant est en fait un navigateur avec page HTML. sinon, prévenez-moi et j'essaierai d'adapter ma solution.
vous pouvez ajouter le champ <input type="hidden" name="complete">
(par exemple) comme paramètre dernier . dans PHP
, vérifiez tout d'abord si ce paramètre a été envoyé par le client. si ce paramètre est envoyé - vous pouvez être sûr que vous avez toutes les données.
maintenant, je ne suis pas sûr que le l'ordre des paramètres doit être préservé selon le RFC (HTML et HTTP). mais j'ai essayé quelques variations et j'ai vu que l'ordre maintenu en effet.
meilleure solution sera, calculer (côté client) hachage des paramètres et lui envoyer comme un autre paramètre. donc vous pouvez être absolument sûr que vous avez toutes les données. Mais cela commence à sembler compliqué...
pour autant que je sache , il n'y a aucun moyen de vérifier si la taille du contenu reçu correspond à la valeur de l'en-tête Content-Length
lorsque vous utilisez multipart/form-data
comme Content-Type
, parce que vous ne pouvez pas mettre la main sur le contenu brut.
1) Si vous pouvez changer Content-Type
(pour application/x-www-form-urlencoded
par exemple), vous pouvez lire php://input
, qui contiendra le contenu brut de la demande. La taille de php://input
doit correspondre à Content-Length
(en supposant que la valeur de Content-Length
est correct.) S'il y a une correspondance, vous pouvez toujours utiliser $_POST
pour obtenir le contenu traité (données POST régulières). Lisez à propos de php://input
ici .
2) ou vous pouvez sérialiser les données sur le client et l'envoyer comme text/plain
. Le serveur peut vérifier la taille de la même manière que décrit ci-dessus. Le serveur devra désérialiser le contenu reçu pour pouvoir travailler avec lui. Et si le client génère un hachage des données sérialisées et l'envoie dans un en-tête ( X-Content-Hash
par exemple), le serveur peut aussi générer un hachage et vérifier s'il correspond à celui de l'en-tête. Vous n'aurez pas besoin de vérifier le hachage, et pouvez être sûr à 100% que le contenu est correct.
3) Si vous ne pouvez pas changer Content-Type
, vous aurez besoin de quelque chose de différent de la taille pour vérifier le contenu. Le client peut utiliser un en-tête supplémentaire (quelque chose comme X-Form-Data-Fields
) pour résumer les champs/clés/noms du contenu que vous envoyez. Le serveur pourrait alors vérifier si tous les champs mentionnés dans l'en-tête sont présents dans le contenu.
4) Une autre solution serait que le client ait une clé/valeur prédéfinie comme dernière entrée dans le contenu. Quelque chose comme:
--boundary
Content-Disposition: form-data; name="_final_field_"
TRUE
--boundary--
Le serveur peut vérifier si ce champ est présent dans le contenu, si le contenu doit être complète.
mise à jour
quand vous avez besoin pour passer des données binaires, vous ne pouvez pas utiliser l'option 1, mais pouvez toujours utiliser l'option 2:
le client peut base64
encoder les entrées binaires, sérialiser les données (avec n'importe quelle technique que vous aimez), générer un hachage des données sérialisées, envoyer le hachage comme l'en-tête et les données comme le corps.
Le serveur peut générer un hachage du contenu reçu, vérifier le hachage avec celui de l'en-tête (et signaler un décalage), désérialiser le contenu, base64
décoder les entrées binaires.
c'est un peu plus de travail qu'en utilisant simplement multipart/form-data
, mais le serveur peut vérifier avec une garantie de 100% que le contenu est le même que ce que le client a envoyé.
si vous pouvez changer l'enctype en
multipart/form-data-alternate
vous pouvez cocher la case
strlen(file_get_contents('php://input'))
vs.
$_SERVER['CONTENT_LENGTH']
ils sont probablement coupés par des limites dans Apache ou PHP. Je crois Qu'Apache a aussi une variable de configuration pour cela.
Voici les paramètres de PHP;
de php.ini
post_max_size=20M
upload_max_filesize=20M
.htaccess
php_value post_max_size 20M
php_value upload_max_filesize 20M
en ce qui concerne les valeurs de formulaire qui sont complètement manquantes en raison de problèmes de connectivité, vous pouvez juste vérifier si elles sont définies:
if(isset($_POST['key']){
//value is set
}else{
//connection was interrupted
}
pour les données grand format (tel qu'un téléchargement d'image) vous pouvez vérifier la taille du fichier reçu en utilisant
$_FILES['key']['size']
une solution simple pourrait utiliser JavaScript pour calculer la taille du fichier du côté du client, et ajouter cette valeur au formulaire comme une entrée cachée sur la soumission du formulaire. Vous obtenez la taille du fichier en JS utilisant quelque chose comme
var filesize = input.files[0].size;
référence: validation de la taille du téléchargement de fichier JavaScript
puis lors du téléchargement du fichier, si la valeur du formulaire caché correspond à la taille du fichier téléchargé, la requête n'a pas été interrompue par des problèmes de connectivité réseau.
peut-être que vous pouvez vérifier avec une variable valide, mais pas la longueur, par exemple:
// client
$clientVars = array('var1' => 'val1', 'otherVar' => 'some value');
ksort($clientVars); // dictionary sorted
$validVar = md5(implode('', $clientVars));
$values = 'var1=val1&otherVar=some value&validVar=' . $validVar;
httpRequest($url, values);
// server
$validVar = $_POST['validVar'];
unset($_POST['validVar']);
ksort($_POST); // dictionary sorted
if (md5(implode('', $_POST)) == $validVar) {
// completed POST, do something
} else {
// not completed POST, log error and do something
}
j'allais également recommander l'utilisation d'une valeur hidden
, ou le hachage comme MeNa mentionne. (le problème est que certains algorithmes sont implémentés différemment sur les plateformes, donc votre CRC32 en js pourrait être différent d'un CRC32 en PHP. Mais avec certains tests, vous devriez être en mesure de trouver un compatible)
je vais suggérer d'utiliser le cryptage symétrique, juste pour le fait que c'est une option. (Je ne crois pas que c'est plus rapide que de hachage). Le cryptage offre, à côté de confidentialité intégrité, c'est à dire. est ce message reçu de celui qui a été envoyé.
bien que les streamciphers soient très rapides, les blockciphers, comme AES peuvent être très rapides aussi, mais cela dépend de votre système, des langues que vous utilisez, etc. (ici aussi, des implémentations différentes signifient que tout le cryptage n'est pas créé égal)
si vous ne pouvez pas déchiffrer le message (ou il donne un désordre confus) que le message était incomplet.
mais sérieusement , utilisez le hachage. hachez le POST sur le client, vérifiez la longueur d'abord du hachage sur le serveur. (quelques-uns?) hashes sont de longueur fixe, donc si la longueur ne correspond pas, c'est faux. Puis hachez le POST reçu et comparez avec le POST-hachage. Si vous le faites sur le POST complet, dans un ordre spécifié (donc toute réorganisation est annulée) la charge est minime.
tout cela, est en supposant que vous ne pouvez pas vérifier le message de poste pour voir si les champs sont manquants et is_set==True, longueur > 0 , !vide.)(..
je pense que ce que vous recherchez est $HTTP_RAW_POST_DATA , cela vous donnera la réelle longueur du POST et puis vous pouvez le comparer à $_SERVER['CONTENT_LENGTH'].
Je ne pense pas qu'il soit possible de calculer la taille du contenu original à partir de $_REQUEST superglobal, au moins pour les requêtes multipart/form-data.
j'ajouterais un en-tête personnalisé à votre requête http avec tout paramètre=valeur hash, à vérifier côté serveur. Headers arriveront à coup sûr de sorte que votre en-tête de hachage est toujours là. Assurez-vous de joindre les paramètres dans le même ordre, sinon de hachage sera différent. Faites également attention à l'encodage, doit être la même sur le client et le serveur.
Si vous pouvez configurer Apache, vous pouvez ajouter un serveur virtuel avec mod_proxy, configuré le proxy sur un autre serveur virtuel sur le même serveur. Ceci devrait filtrer les requêtes incomplètes. Notez que vous gaspillez 2 sockets par demande de cette façon, alors gardez un oeil sur l'utilisation des ressources si vous pensez à aller de cette façon.
une autre solution qui pourrait être utile... Si la connexion de l'autre côté est lente, il suffit de supprimer la limite pour exécuter le poteau.
set_time_limit(0);
et vous serez sûr que les données du trou post seront envoyées.
si calculer la longueur du contenu n'est pas raisonnable, vous pourriez probablement vous en tirer en signant les données envoyées par le client.
utiliser javascript, sérialiser les données de forme à une chaîne json ou l'équivalent d'une manière raisonnablement saine (c.-à-d. le trier au besoin) avant de soumettre. Hachez cette chaîne de caractères en utilisant un ou deux algorithmes raisonnablement rapides (par exemple crc32, md5, sha1), et ajoutez ces données de hachage supplémentaires à ce qui est sur le point d'être envoyé comme signature.
sur le serveur, supprime ces données de hachage supplémentaires de la requête $_POST, puis refait le même travail en PHP. Comparez les hashs en conséquence: rien n'a été perdu dans la traduction si les hashs correspondent. (Utiliser deux hachages si vous souhaitez annuler la minuscule risque d'obtenir des faux positifs.)
je parierais qu'il y a un moyen raisonnable de faire quelque chose de similaire pour les fichiers, par exemple en récupérant leur nom et leur taille dans JS, et en ajoutant cette information supplémentaire aux données qui sont signées.
ceci est en quelque sorte lié à ce que font certains cadres PHP pour éviter d'altérer les données de session, lorsque ces dernières sont gérées et stockées dans des cookies côté client, donc vous trouverez probablement du code facilement disponible pour le faire dans ce dernier contexte.
réponse originale:
pour autant que je sache, la différence entre envoyer une demande GET ou un POST plus ou moins à des montants d'envoyer quelque chose comme:
GET /script.php?var1=foo&var2=bar
headers
vs envoyer quelque chose comme:
POST /script.php
headers
var1=foo&var2=bar <— content length is the length of this chunk
donc pour chaque partie, vous pouvez calculer la longueur et vérifier que vs La longueur annoncée par l'en-tête content-length.
-
$_FILES
les entrées ont un champ taille pratique que vous pouvez utiliser directement. - pour
$_POST
données, reconstruire la chaîne de requête qui a été envoyé et calculer sa longueur.
Points à méfiez-vous de:
- vous devez savoir comment les données devraient être envoyées dans certains cas, par exemple
var[]=foo&var[]=baz
vsvar[0]=foo&var[1]=baz
- vous avez affaire à la longueur de C-string plutôt qu'à la longueur multibyte dans ce dernier cas. (Bien que, je ne serais pas surpris d'apprendre qu'un navigateur bizarre se comporte de façon incohérente ici et là.)
"autres lectures:
c'est un bug connu en PHP et doit être corrigé - https://bugs.php.net/bug.php?id=61471
essayez d'utiliser le tampon de sortie avec ob_start(). Alors que la mise en tampon de sortie est active, aucune sortie n'est envoyée à partir du script (autre que les en-têtes), à la place la sortie est stockée dans un tampon interne.
le contenu de ce tampon interne peut être copié dans une variable de chaîne en utilisant ob_get_contents(). Pour afficher ce qui est stocké dans le tampon interne, utilisez ob_end_flush(). Alternativement, ob_end_clean () rejettera silencieusement le contenu du tampon.