H. Carpeta « php »

Versión para imprimir.

A. php / procesa.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/recibeTextoObligatorio.php";
5
require_once __DIR__ . "/lib/validaToken.php";
6
require_once __DIR__ . "/lib/devuelveJson.php";
7
8
session_start();
9
10
$token = $_POST["token"];
11
validaToken("formulario", $token);
12
13
// Si el token se halló, precesa normalmente la forma.
14
15
$saludo = recibeTextoObligatorio("saludo");
16
$nombre = recibeTextoObligatorio("nombre");
17
18
$resultado = "{$saludo} {$nombre}.";
19
20
devuelveJson($resultado);
21

B. php / vista-formulario.php

1
<?php
2
3
require_once __DIR__ . "/lib/manejaErrores.php";
4
require_once __DIR__ . "/lib/creaToken.php";
5
require_once __DIR__ . "/lib/devuelveJson.php";
6
7
session_start();
8
// Crea un token para la página "formulario" que expira en 5 minutos.
9
devuelveJson(["token" => ["value" => creaToken("formulario", 5)]]);
10

C. Carpeta « php / lib »

Versión para imprimir.

1. php / lib / BAD_REQUEST.php

1
<?php
2
3
const BAD_REQUEST = 400;
4

2. php / lib / creaToken.php

1
<?php
2
3
require_once __DIR__ . "/FORBIDDEN.php";
4
require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
5
require_once __DIR__ . "/ProblemDetailsException.php";
6
7
function creaToken(string $pagina, int $duracionEnMinutos)
8
{
9
 $criptografiaFuerte = true;
10
11
 // Crea el token
12
 $token = [
13
  "expiracion" => time() + 60 * $duracionEnMinutos,
14
  // El token es de 80 caracteres, criptográficamente fuerte.
15
  "texto" => bin2hex(openssl_random_pseudo_bytes(80, $criptografiaFuerte))
16
 ];
17
18
 // Verifica que ya haya tokens $pagina.
19
 if (isset($_SESSION[$pagina])) {
20
21
  $tokensParaPagina = $_SESSION[$pagina];
22
23
  if (!is_array($tokensParaPagina))
24
   throw new ProblemDetailsException([
25
    "status" => INTERNAL_SERVER_ERROR,
26
    " title" => "No hay arreglo de tokens.",
27
    "type" => "/error/sintokens.html",
28
   ]);
29
30
  // Como ya existe el arreglo, elimina los tokens expirados para esta pagina.
31
  $time = time();
32
  foreach ($tokensParaPagina as $llave => $tokenParaPagina) {
33
   if ($tokenParaPagina["expiracion"] > $time) {
34
    unset($tokensParaPagina[$llave]);
35
   }
36
  }
37
38
  // Se puede usar uno o varios tokens por pagina.
39
  $tokensParaPagina[] = $token;
40
  $_SESSION[$pagina] = $tokensParaPagina;
41
 } else {
42
43
  // Se puede usar uno o varios tokens por pagina 
44
  $_SESSION[$pagina] = [$token];
45
 }
46
47
 return $token["texto"];
48
}
49

3. php / lib / devuelveJson.php

1
<?php
2
3
require_once __DIR__ . "/devuelveResultadoNoJson.php";
4
5
function devuelveJson($resultado)
6
{
7
 $json = json_encode($resultado);
8
 if ($json === false) {
9
  devuelveResultadoNoJson();
10
 } else {
11
  header("Content-Type: application/json; charset=utf-8");
12
  echo $json;
13
 }
14
 exit();
15
}
16

4. php / lib / devuelveResultadoNoJson.php

1
<?php
2
3
require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4
5
function devuelveResultadoNoJson()
6
{
7
 http_response_code(INTERNAL_SERVER_ERROR);
8
 header("Content-Type: application/problem+json; charset=utf-8");
9
10
 echo '{' .
11
  "status: " . INTERNAL_SERVER_ERROR .
12
  '"title": "El resultado no puede representarse como JSON."' .
13
  '"type": "/errors/resultadonojson.html"' .
14
  '}';
15
}
16

5. php / lib / FORBIDDEN.php

1
<?php
2
3
const FORBIDDEN = 403;
4

6. php / lib / INTERNAL_SERVER_ERROR.php

1
<?php
2
3
const INTERNAL_SERVER_ERROR = 500;

7. php / lib / manejaErrores.php

1
<?php
2
3
require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4
require_once __DIR__ . "/ProblemDetailsException.php";
5
6
// Hace que se lance una excepción automáticamente cuando se genere un error.
7
set_error_handler(function ($severity, $message, $file, $line) {
8
 throw new ErrorException($message, 0, $severity, $file, $line);
9
});
10
11
// Código cuando una excepción no es atrapada.
12
set_exception_handler(function (Throwable $excepcion) {
13
 if ($excepcion instanceof ProblemDetailsException) {
14
  devuelveProblemDetails($excepcion->problemDetails);
15
 } else {
16
  devuelveProblemDetails([
17
   "status" => INTERNAL_SERVER_ERROR,
18
   "title" => "Error interno del servidor",
19
   "detail" => $excepcion->getMessage(),
20
   "type" => "/errors/errorinterno.html",
21
  ]);
22
 }
23
 exit();
24
});
25
26
function devuelveProblemDetails(array $array)
27
{
28
 $json = json_encode($array);
29
 if ($json === false) {
30
  devuelveResultadoNoJson();
31
 } else {
32
  http_response_code(isset($array["status"]) ? $array["status"] : 500);
33
  header("Content-Type: application/problem+json; charset=utf-8");
34
  echo $json;
35
 }
36
}
37

8. php / lib / ProblemDetailsException.php

1
<?php
2
3
require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
4
5
/**
6
 * Detalle de los errores devueltos por un servicio.
7
 */
8
class ProblemDetailsException extends Exception
9
{
10
11
 public array $problemDetails;
12
13
 public function __construct(
14
  array $problemDetails,
15
 ) {
16
  
17
  parent::__construct(
18
   isset($problemDetails["detail"])
19
    ? $problemDetails["detail"]
20
    : (isset($problemDetails["title"])
21
     ? $problemDetails["title"]
22
     : "Error"),
23
   $problemDetails["status"]
24
    ? $problemDetails["status"]
25
    : INTERNAL_SERVER_ERROR
26
  );
27
28
  $this->problemDetails = $problemDetails;
29
 }
30
}
31

9. php / lib / recibeTexto.php

1
<?php
2
3
/**
4
 * Devuelve el texto de un parámetro enviado al
5
 * servidor por medio de GET, POST o cookie.
6
 * 
7
 * Si el parámetro no se recibe, devuelve false.
8
 */
9
function recibeTexto(string $parametro): false|string
10
{
11
 /* Si el parámetro está asignado en $_REQUEST,
12
  * devuelve su valor; de lo contrario, devuelve false.
13
  */
14
 $valor = isset($_REQUEST[$parametro])
15
  ? $_REQUEST[$parametro]
16
  : false;
17
 return $valor;
18
}
19

10. php / lib / recibeTextoObligatorio.php

1
<?php
2
3
require_once __DIR__ . "/BAD_REQUEST.php";
4
require_once __DIR__ . "/recibeTexto.php";
5
require_once __DIR__ . "/ProblemDetailsException.php";
6
7
function recibeTextoObligatorio(string $parametro)
8
{
9
 $texto = recibeTexto($parametro);
10
11
 if ($texto === false)
12
  throw new ProblemDetailsException([
13
   "status" => BAD_REQUEST,
14
   "title" => "Falta el valor $parametro.",
15
   "type" => "/errors/faltavalor.html",
16
   "detail" => "La solicitud no tiene el valor de $parametro."
17
  ]);
18
19
 $trimTexto = trim($texto);
20
21
 if ($trimTexto === "")
22
  throw new ProblemDetailsException([
23
   "status" => BAD_REQUEST,
24
   "title" => "Campo $parametro en blanco.",
25
   "type" => "/errors/campoenblanco.html",
26
   "detail" => "Pon texto en el campo $parametro."
27
  ]);
28
29
 return $trimTexto;
30
}
31

11. php / lib / validaToken.php

1
<?php
2
3
require_once __DIR__ . "/FORBIDDEN.php";
4
require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php";
5
require_once __DIR__ . "/ProblemDetailsException.php";
6
7
function validaToken(string $pagina, string $token)
8
{
9
 if (!isset($_SESSION[$pagina]))
10
  throw new ProblemDetailsException([
11
   "status" => FORBIDDEN,
12
   "title" => "Página no registrada.",
13
   "type" => "/error/paginanoregistrada.html",
14
  ]);
15
16
 $tokensParaPagina = $_SESSION[$pagina];
17
18
 if (!is_array($tokensParaPagina))
19
  throw new ProblemDetailsException([
20
   "status" => INTERNAL_SERVER_ERROR,
21
   " title" => "No hay arreglo de tokens.",
22
   "type" => "/error/sintokens.html",
23
  ]);
24
25
 $hallado = false;
26
27
 // Valida que el token se haya registrado.
28
  $time = time();
29
 foreach ($tokensParaPagina as $llave => $tokenParaPagina) {
30
31
  if (strcmp($token, $tokenParaPagina["texto"]) === 0) {
32
33
   if ($tokenParaPagina["expiracion"] < $time) {
34
    unset($tokensParaPagina[$llave]);
35
    $_SESSION[$pagina] = $tokensParaPagina;
36
    throw new ProblemDetailsException([
37
     "status" => FORBIDDEN,
38
     "title" => "Tiempo de expiración excedido.",
39
     "type" => "/error/paginaexpirada.html",
40
    ]);
41
   }
42
43
   $hallado = true;
44
  } elseif ($tokenParaPagina["expiracion"] > $time) {
45
46
   // Elimina tokens expirados
47
   unset($tokensParaPagina[$llave]);
48
  }
49
 }
50
51
 $_SESSION[$pagina] = $tokensParaPagina;
52
53
 if ($hallado === false)
54
  throw new ProblemDetailsException([
55
   "status" => FORBIDDEN,
56
   "title" => "Página no registrada.",
57
   "type" => "/error/paginanoregistrada.html",
58
  ]);
59
}
60