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?

8
demandé sur spatar 2013-11-28 03:57:07

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é...

3
répondu MeNa 2013-12-04 09:41:48

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é.

1
répondu Jasper N. Brouwer 2013-12-09 09:10:43

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']
1
répondu corretge 2013-12-10 10:58:12

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
0
répondu vokx 2013-11-28 00:05:41

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.

0
répondu nightowl 2017-05-23 10:30:51

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
}
0
répondu andy.why 2013-12-05 14:58:59

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.)(..

0
répondu puredevotion 2013-12-08 20:02:55

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'].

0
répondu Pilingo 2013-12-09 23:03:47

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.

0
répondu Ghigo 2013-12-10 11:12:32

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.

0
répondu ventsi.slav 2013-12-10 16:27:43

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:

  1. vous devez savoir comment les données devraient être envoyées dans certains cas, par exemple var[]=foo&var[]=baz vs var[0]=foo&var[1]=baz
  2. 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:

0
répondu Denis de Bernardy 2013-12-10 18:09:41

c'est un bug connu en PHP et doit être corrigé - https://bugs.php.net/bug.php?id=61471

0
répondu mp_de 2017-12-12 15:15:48

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.

-1
répondu Rajiv Charan Tej K 2013-12-10 18:14:08