Validación de entradas de Crontab con PHP
regex validation (7)
Deberías poder hacer eso bastante fácilmente con regex. De hecho, no me sorprendería si pudieras encontrar una expresión regular para eso en Google. Esto no se ha probado, pero tal vez algo así como:
/^((/*)|(/d+((-/d+)|(,/d+)+))/s+){5}/
¿Cuál es la mejor manera de validar una entrada de crontab con PHP? ¿Debo usar una expresión regular o una biblioteca externa? Tengo un script PHP que agrega / elimina entradas de un archivo crontab, pero quiero tener alguna forma de verificar que la porción de intervalo de tiempo esté en un formato válido.
Hmmm, problema interesante.
Si realmente va a validarlo, Regex no será suficiente, tendrá que analizar realmente la entrada y validar cada uno de los bits de programación. Esto se debe a que cada bit puede ser un número, una cadena de mes / día de la semana, un rango (2-7), un conjunto (3, 4, sábado), un atajo de estilo cron de Vixie (60/5) o cualquier combinación de lo anterior: cualquier abordaje regex único se pondrá muy peludo, rápido.
¡Simplemente usar el programa crontab
de Vixie cron para validar no es suficiente, porque en realidad no se valida por completo! Puedo obtener crontab
para aceptar todo tipo de cosas ilegales.
Los guiones Wicked Cool Shell de Dave Taylor ( enlace de Google books ) tienen un guión sh que hace una validación parcial. La discusión me pareció interesante. También puede usar o adaptar el código.
También encontré enlaces a dos clases de PHP que hacen lo que dices (cuya calidad no he evaluado):
- http://www.phpclasses.org/browse/package/1189.html
- http://www.phpclasses.org/browse/package/1985.html
Otro enfoque (dependiendo de lo que tu aplicación necesite hacer) podría ser hacer que PHP construya la entrada crontab programáticamente e insertarla, para que sepas que siempre es válida, en lugar de intentar validar una cadena que no sea de confianza. Entonces solo necesitarías hacer una UI de "compilación de una entrada crontab", que podría ser simple si no necesitas combinaciones de programación realmente complicadas.
¿Quién dijo que las expresiones regulares no pueden hacer eso?
Cortesía de mi empleador, Salir.com , aquí hay una prueba de PHPUnit que hace dicha validación. Siéntase libre de modificar y distribuir. Le agradeceré si mantiene el @author notice & link al sitio web.
<?php
/**
* @author Jordi Salvat i Alabart - with thanks to <a href="www.salir.com">Salir.com</a>.
*/
abstract class CrontabChecker extends PHPUnit_Framework_TestCase {
protected function assertFileIsValidUserCrontab($file) {
$f= @fopen($file, ''r'', 1);
$this->assertTrue($f !== false, ''Crontab file must exist'');
while (($line= fgets($f)) !== false) {
$this->assertLineIsValid($line);
}
}
protected function assertLineIsValid($line) {
$regexp= $this->buildRegexp();
$this->assertTrue(preg_match("/$regexp/", $line) !== 0);
}
private function buildRegexp() {
$numbers= array(
''min''=>''[0-5]?/d'',
''hour''=>''[01]?/d|2[0-3]'',
''day''=>''0?[1-9]|[12]/d|3[01]'',
''month''=>''[1-9]|1[012]'',
''dow''=>''[0-7]''
);
foreach($numbers as $field=>$number) {
$range= "($number)(-($number)(///d+)?)?";
$field_re[$field]= "/*(///d+)?|$range(,$range)*";
}
$field_re[''month''].=''|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec'';
$field_re[''dow''].=''|mon|tue|wed|thu|fri|sat|sun'';
$fields_re= ''(''.join('')/s+('', $field_re).'')'';
$replacements= ''@reboot|@yearly|@annually|@monthly|@weekly|@daily|@midnight|@hourly'';
return ''^/s*(''.
''$''.
''|#''.
''|/w+/s*=''.
"|$fields_re/s+/S".
"|($replacements)/s+/S".
'')'';
}
}
Gracias a Jordi Salvat i Alabart que publicó una gran solución.
Solo he modificado la solución existente publicada por Jordi Salvat i Alabart. Me funcionó bien, pero quería extraer partes particulares mediante la captura de grupos. He agregado paréntesis que no capturan para poder extraer partes particulares del registro de crontab. Es fácil ver qué grupo de captura usar cuando pruebe la salida de datos regex en: http://www.regexplanet.com/advanced/java/index.html
<?php
/**
* @author Jordi Salvat i Alabart - with thanks to <a href="www.salir.com">Salir.com</a>.
*/
function buildRegexp() {
$numbers = array(
''min'' => ''[0-5]?/d'',
''hour'' => ''[01]?/d|2[0-3]'',
''day'' => ''0?[1-9]|[12]/d|3[01]'',
''month'' => ''[1-9]|1[012]'',
''dow'' => ''[0-6]''
);
foreach ($numbers as $field => $number) {
$range = "(?:$number)(?:-(?:$number)(?:///d+)?)?";
$field_re[$field] = "/*(?:///d+)?|$range(?:,$range)*";
}
$field_re[''month''].=''|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec'';
$field_re[''dow''].=''|mon|tue|wed|thu|fri|sat|sun'';
$fields_re = ''('' . join('')/s+('', $field_re) . '')'';
$replacements = ''@reboot|@yearly|@annually|@monthly|@weekly|@daily|@midnight|@hourly'';
return ''^/s*('' .
''$'' .
''|#'' .
''|/w+/s*='' .
"|$fields_re/s+" .
"|($replacements)/s+" .
'')'' .
''([^//s]+)//s+'' .
''(.*)$'';
}
Este código genera expresiones regulares:
^/s*($|#|/w+/s*=|(/*(?:///d+)?|(?:[0-5]?/d)(?:-(?:[0-5]?/d)(?:///d+)?)?(?:,(?:[0-5]?/d)(?:-(?:[0-5]?/d)(?:///d+)?)?)*)/s+(/*(?:///d+)?|(?:[01]?/d|2[0-3])(?:-(?:[01]?/d|2[0-3])(?:///d+)?)?(?:,(?:[01]?/d|2[0-3])(?:-(?:[01]?/d|2[0-3])(?:///d+)?)?)*)/s+(/*(?:///d+)?|(?:0?[1-9]|[12]/d|3[01])(?:-(?:0?[1-9]|[12]/d|3[01])(?:///d+)?)?(?:,(?:0?[1-9]|[12]/d|3[01])(?:-(?:0?[1-9]|[12]/d|3[01])(?:///d+)?)?)*)/s+(/*(?:///d+)?|(?:[1-9]|1[012])(?:-(?:[1-9]|1[012])(?:///d+)?)?(?:,(?:[1-9]|1[012])(?:-(?:[1-9]|1[012])(?:///d+)?)?)*|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/s+(/*(?:///d+)?|(?:[0-6])(?:-(?:[0-6])(?:///d+)?)?(?:,(?:[0-6])(?:-(?:[0-6])(?:///d+)?)?)*|mon|tue|wed|thu|fri|sat|sun)/s+|(@reboot|@yearly|@annually|@monthly|@weekly|@daily|@midnight|@hourly)/s+)([^/s]+)/s+(.*)$
O alternativa de Java para generar esta expresión regular (sin cosas @X):
public static String buildRegex(){
// numbers intervals and regex
Map<String, String> numbers = new HashMap<String, String>();
numbers.put("min", "[0-5]?//d");
numbers.put("hour", "[01]?//d|2[0-3]");
numbers.put("day", "0?[1-9]|[12]//d|3[01]");
numbers.put("month", "[1-9]|1[012]");
numbers.put("dow", "[0-6]");
Map<String, String> field_re = new HashMap<String, String>();
// expand regex to contain different time specifiers
for(String field : numbers.keySet()){
String number = numbers.get(field);
String range = "(?:"+number+")(?:-(?:"+number+")(?://///d+)?)?";
field_re.put(field, "//*(?://///d+)?|"+range+"(?:,"+range+")*");
}
// add string specifiers
String monthRE = field_re.get("month");
monthRE = monthRE + "|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec";
field_re.put("month", monthRE);
String dowRE = field_re.get("dow");
dowRE = dowRE + "|mon|tue|wed|thu|fri|sat|sun";
field_re.put("dow", dowRE);
StringBuilder fieldsReSB = new StringBuilder();
fieldsReSB.append("^//s*(")
.append("$")
.append("|#")
.append("|//w+//s*=")
.append("|");
.append("(")
.append(field_re.get("min")).append(")//s+(")
.append(field_re.get("hour")).append(")//s+(")
.append(field_re.get("day")).append(")//s+(")
.append(field_re.get("month")).append(")//s+(")
.append(field_re.get("dow"))
.append(")")
.append("//s+)")
.append("([^//s]+)//s+")
.append("(.*)$");
return fieldsReSB.toString();
}
Gracias a Jordi Salvat i Alabart y ph4r05.
Tengo una pequeña solución modificada existente publicada en php. Alternativa Perl para generar expresiones regex:
sub _BuildRegex {
my $number = {
''min'' => ''[0-5]?/d'',
''hour'' => ''[01]?/d|2[0-3]'',
''day'' => ''0?[1-9]|[12]/d|3[01]'',
''month'' => ''[1-9]|1[012]'',
''dow'' => ''[0-6]''
};
my $field_re = {};
foreach my $nmb ( qw/min hour day month dow/ ) {
my $range = "(?:$number->{$nmb})(?:-(?:$number->{$nmb})(?://///d+)?)?";
$field_re->{$nmb} = "//*(?://///d+)?|$range(?:,$range)*";
}
$field_re->{''month''} .=''|[jJ]an|[fF]eb|[mM]ar|[aA]pr|[mM]ay|[jJ]un|[jJ]ul|[aA]ug|[sS]ep|[oO]ct|[nN]ov|[dD]ec'';
$field_re->{''dow''} .= ''|[mM]on|[tT]ue|[wW]ed|[tT]hu|[fF]ri|[sS]at|[sS]un'';
my $ff = [];
push @$ff, $field_re->{$_} foreach ( qw/min hour day month dow/ );
my $fields_req = ''('' . join('')/s+('', @$ff) . '')'';
my $replacements = ''@reboot|@yearly|@annually|@monthly|@weekly|@daily|@midnight|@hourly'';
return ''^/s*('' .
''$'' .
''|#'' .
''|/w+/s*='' .
"|$fields_req//s+" .
"|($replacements)//s+" .
'')'' .
''([^//s]+)//s+'' .
''(.*)$'';
}
Use el patrón: /^((?:[1-9]?/d|/*)/s*(?:(?:[//-][1-9]?/d)|(?:,[1-9]?/d)+)?/s*){5}$/
En PHP:
<?php
$cron = "*/5 1-2 3 3,4,5 *";
$result = preg_match( "/^((?:[1-9]?/d|/*)/s*(?:(?:[//-][1-9]?/d)|(?:,[1-9]?/d)+)?/s*){5}$/", $cron, $matches);
print_r($matches);
Hay una buena biblioteca de PHP que se puede usar para la validación de la expresión de Cron:
Para instalar esta biblioteca a través del compositor:
composer require mtdowling/cron-expression
Para verificar si la expresión de Cron es válida
$isValid = Cron/CronExpression::isValidExpression($expression);