FTN Packet inspection
This commit is contained in:
parent
cad523577e
commit
5753982a8d
31
app/Classes/FTN.php
Normal file
31
app/Classes/FTN.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Classes;
|
||||||
|
|
||||||
|
abstract class FTN
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if a line is a kludge line.
|
||||||
|
*
|
||||||
|
* @param string $kludge
|
||||||
|
* @param string $string
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function kludge(string $kludge,string $string)
|
||||||
|
{
|
||||||
|
return (preg_match("/^{$kludge}/",$string))
|
||||||
|
? chop(preg_replace("/^{$kludge}/",'',$string),"\r")
|
||||||
|
: FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function creates our unpack header
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function unpackheader(array $pack)
|
||||||
|
{
|
||||||
|
return join('/',array_values(collect($pack)
|
||||||
|
->sortBy(function($k,$v) {return $k[0];})
|
||||||
|
->transform(function($k,$v) {return $k[1].$v;})->toArray()));
|
||||||
|
}
|
||||||
|
}
|
251
app/Classes/FTNMessage.php
Normal file
251
app/Classes/FTNMessage.php
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Classes;
|
||||||
|
|
||||||
|
use App\Exceptions\InvalidFidoPacketException;
|
||||||
|
|
||||||
|
class FTNMessage extends FTN
|
||||||
|
{
|
||||||
|
private $src = NULL;
|
||||||
|
private $dst = NULL;
|
||||||
|
private $flags = NULL;
|
||||||
|
private $cost = 0;
|
||||||
|
|
||||||
|
private $from = NULL; // FTS-0001.016 From Name: upto 36 chars null terminated
|
||||||
|
private $to = NULL; // FTS-0001.016 To Name: upto 36 chars null terminated
|
||||||
|
private $subject = NULL; // FTS-0001.016 Subject: upto 72 chars null terminated
|
||||||
|
private $date = NULL; // FTS-0001.016 Date: upto 20 chars null terminated
|
||||||
|
|
||||||
|
private $message = NULL; // The actual message content
|
||||||
|
private $echoarea = NULL; // FTS-0004.001
|
||||||
|
private $intl = NULL;
|
||||||
|
private $msgid = NULL;
|
||||||
|
private $reply = NULL; // Message thread reply source
|
||||||
|
private $origin = NULL; // FTS-0004.001
|
||||||
|
|
||||||
|
private $kludge = []; // Hold kludge items
|
||||||
|
private $path = []; // FTS-0004.001
|
||||||
|
private $seenby = []; // FTS-0004.001
|
||||||
|
private $via = [];
|
||||||
|
private $_other = [];
|
||||||
|
private $unknown = [];
|
||||||
|
|
||||||
|
private $fqfa = NULL; // Fully qualified fidonet source where packet originated
|
||||||
|
private $fqfd = NULL; // Fully qualified fidonet destination address (Netmail)
|
||||||
|
|
||||||
|
// Single value kludge items
|
||||||
|
private $_kludge = [
|
||||||
|
'chrs' => 'CHRS: ',
|
||||||
|
'charset' => 'CHARSET: ',
|
||||||
|
'codepage' => 'CODEPAGE: ',
|
||||||
|
'pid' => 'PID: ',
|
||||||
|
'tid' => 'TID: ',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(string $header)
|
||||||
|
{
|
||||||
|
// Initialise vars
|
||||||
|
$this->kludge = collect(); // The message kludge lines
|
||||||
|
$this->path = collect(); // The message PATH lines
|
||||||
|
$this->seenby = collect(); // The message SEEN-BY lines
|
||||||
|
$this->via = collect(); // The path the message has gone using Via lines
|
||||||
|
$this->_other = collect(); // Temporarily hold attributes we dont process yet.
|
||||||
|
$this->unknown = collect(); // Temporarily hold attributes we have no logic for.
|
||||||
|
|
||||||
|
// FTS-0001.016 Message header 12 bytes
|
||||||
|
// node, net, flags, cost
|
||||||
|
$struct = [
|
||||||
|
'onode'=>[0x00,'v',2],
|
||||||
|
'dnode'=>[0x02,'v',2],
|
||||||
|
'onet'=>[0x04,'v',2],
|
||||||
|
'dnet'=>[0x06,'v',2],
|
||||||
|
'flags'=>[0x08,'v',2],
|
||||||
|
'cost'=>[0x0a,'v',2],
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = unpack($this->unpackheader($struct),$header);
|
||||||
|
|
||||||
|
$this->src = sprintf('%s/%s',array_get($result,'onet'),array_get($result,'onode'));
|
||||||
|
$this->dst = sprintf('%s/%s',array_get($result,'dnet'),array_get($result,'dnode'));
|
||||||
|
$this->flags = array_get($result,'flags');
|
||||||
|
$this->cost = array_get($result,'cost');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get($k)
|
||||||
|
{
|
||||||
|
return $this->{$k};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __set($k,$v)
|
||||||
|
{
|
||||||
|
switch ($k)
|
||||||
|
{
|
||||||
|
case 'message':
|
||||||
|
// Remove DOS \n\r
|
||||||
|
$v = preg_replace("/\n\r/","\r",$v);
|
||||||
|
|
||||||
|
$this->parsemessage($v);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'origin':
|
||||||
|
$this->parseorigin($v);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$this->{$k} = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parsemessage(string $message)
|
||||||
|
{
|
||||||
|
// Split out the <SOH> lines
|
||||||
|
$result = collect(explode("\01",$message))->filter();
|
||||||
|
|
||||||
|
foreach ($result as $k => $v)
|
||||||
|
{
|
||||||
|
// Search for \r - if that is the end of the line, then its a kludge
|
||||||
|
$x = strpos($v,"\r");
|
||||||
|
|
||||||
|
// If there are more characters, then put the kludge back into the result, so that we process it.
|
||||||
|
if ($x != strlen($v)-1)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Anything after the origin line is also kludge data.
|
||||||
|
*/
|
||||||
|
if ($y = strpos($v,"\r * Origin: "))
|
||||||
|
{
|
||||||
|
$this->message .= substr($v,$x+1,$y-$x-1);
|
||||||
|
$this->__set('origin',substr($v,$y));
|
||||||
|
$matches = [];
|
||||||
|
|
||||||
|
preg_match('/^.*\((.*)\)$/',$this->origin,$matches);
|
||||||
|
|
||||||
|
if (($this->type() == 'Netmail' AND array_get($matches,1) != $this->fqfa) OR ! array_get($matches,1))
|
||||||
|
throw new InvalidFidoPacketException(sprintf('Source address mismatch? [%s,%s]',$this->fqfa,array_get($matches,1)));
|
||||||
|
|
||||||
|
$this->fqfa = array_get($matches,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$v = substr($v,0,$x+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->_kludge as $a => $b) {
|
||||||
|
if ($t = $this->kludge($b, $v)) {
|
||||||
|
$this->kludge->put($a,$t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($t)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ($t = $this->kludge('AREA:',$v))
|
||||||
|
$this->echoarea = $t;
|
||||||
|
|
||||||
|
// From point: <SOH>"FMPT <point number><CR>
|
||||||
|
elseif ($t = $this->kludge('FMPT ',$v))
|
||||||
|
$this->_other->push($t);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The INTL control paragraph shall be used to give information about
|
||||||
|
* the zone numbers of the original sender and the ultimate addressee
|
||||||
|
* of a message.
|
||||||
|
*
|
||||||
|
* <SOH>"INTL "<destination address>" "<origin address><CR>
|
||||||
|
*/
|
||||||
|
elseif ($t = $this->kludge('INTL ',$v))
|
||||||
|
{
|
||||||
|
$this->intl = $t;
|
||||||
|
list($this->fqfd,$this->fqfa) = explode(' ',$t);
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif ($t = $this->kludge('MSGID: ',$v))
|
||||||
|
$this->msgid = $t;
|
||||||
|
|
||||||
|
elseif ($t = $this->kludge('PATH: ',$v))
|
||||||
|
$this->path->push($t);
|
||||||
|
|
||||||
|
elseif ($t = $this->kludge('REPLY: ',$v))
|
||||||
|
$this->reply = $t;
|
||||||
|
|
||||||
|
// To Point: <SOH>TOPT <point number><CR>
|
||||||
|
elseif ($t = $this->kludge('TOPT ',$v))
|
||||||
|
$this->_other->push($t);
|
||||||
|
|
||||||
|
// Time Zone of the sender.
|
||||||
|
elseif ($t = $this->kludge('TZUTC: ',$v))
|
||||||
|
$this->tzutc= $t;
|
||||||
|
|
||||||
|
// <SOH>Via <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [Serial Number]<CR>
|
||||||
|
elseif ($t = $this->kludge('Via ',$v))
|
||||||
|
$this->via->push($t);
|
||||||
|
|
||||||
|
// We got a kludge line we dont know about
|
||||||
|
else {
|
||||||
|
$this->unknown->push(chop($v,"\r"));
|
||||||
|
|
||||||
|
//dd(['v'=>$v,'t'=>$t]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the data after the ORIGIN
|
||||||
|
* There may be kludge lines after the origin - notably SEEN-BY
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function parseorigin(string $message)
|
||||||
|
{
|
||||||
|
// Split out each line
|
||||||
|
$result = collect(explode("\r",$message))->filter();
|
||||||
|
|
||||||
|
foreach ($result as $k => $v) {
|
||||||
|
|
||||||
|
foreach ($this->_kludge as $a => $b) {
|
||||||
|
if ($t = $this->kludge($b, $v)) {
|
||||||
|
$this->kludge->put($a, $t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($t = $this->kludge('SEEN-BY: ', $v))
|
||||||
|
$this->seenby->push($t);
|
||||||
|
|
||||||
|
elseif ($t = $this->kludge('PATH: ', $v))
|
||||||
|
$this->path->push($t);
|
||||||
|
|
||||||
|
elseif ($t = $this->kludge(' \* Origin: ',$v))
|
||||||
|
$this->origin = $t;
|
||||||
|
|
||||||
|
// We got unknown Kludge lines in the origin
|
||||||
|
else {
|
||||||
|
$this->unknown->push($v);
|
||||||
|
|
||||||
|
//dd(['v'=>$v,'t'=>$t,'message'=>$message]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description()
|
||||||
|
{
|
||||||
|
switch ($this->type())
|
||||||
|
{
|
||||||
|
case 'Echomail': return sprintf('Echomail: '.$this->echoarea);
|
||||||
|
case 'Netmail': return sprintf('Netmail: %s->%s',$this->fqfa,$this->fqfd);
|
||||||
|
default:
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function type()
|
||||||
|
{
|
||||||
|
if ($this->echoarea)
|
||||||
|
return 'Echomail';
|
||||||
|
|
||||||
|
if ($this->intl)
|
||||||
|
return 'Netmail';
|
||||||
|
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
}
|
159
app/Classes/FTNPacket.php
Normal file
159
app/Classes/FTNPacket.php
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Classes;
|
||||||
|
|
||||||
|
use App\Exceptions\InvalidFidoPacketException;
|
||||||
|
|
||||||
|
class FTNPacket extends FTN
|
||||||
|
{
|
||||||
|
public $pktsrc = NULL;
|
||||||
|
public $pktdst = NULL;
|
||||||
|
private $pktver = NULL;
|
||||||
|
public $date = NULL;
|
||||||
|
private $baud = NULL;
|
||||||
|
private $proddata = NULL;
|
||||||
|
private $password = NULL;
|
||||||
|
|
||||||
|
public $filename = NULL;
|
||||||
|
public $messages = [];
|
||||||
|
|
||||||
|
public function __construct(string $file)
|
||||||
|
{
|
||||||
|
$this->filename = $file;
|
||||||
|
|
||||||
|
if ($file)
|
||||||
|
return $this->OpenFile($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a packet file
|
||||||
|
*
|
||||||
|
* @param string $file
|
||||||
|
* @throws InvalidFidoPacketException
|
||||||
|
*/
|
||||||
|
private function OpenFile(string $file)
|
||||||
|
{
|
||||||
|
$f = fopen($file,'r');
|
||||||
|
// $fstat = fstat($f);
|
||||||
|
|
||||||
|
// PKT Header
|
||||||
|
$header = fread($f,0x3a);
|
||||||
|
|
||||||
|
// Could not read header
|
||||||
|
if (strlen($header) != 0x3a)
|
||||||
|
throw new InvalidFidoPacketException('Length of Header too short: '.$file);
|
||||||
|
|
||||||
|
// Not a type 2 packet
|
||||||
|
if (array_get(unpack('vv',substr($header,0x12)),'v') != 2)
|
||||||
|
throw new InvalidFidoPacketException('Not a type 2 packet:'. $file);
|
||||||
|
|
||||||
|
$this->setHeader($header);
|
||||||
|
$this->messages = collect();
|
||||||
|
|
||||||
|
while (! feof($f))
|
||||||
|
{
|
||||||
|
$x = fread($f,2);
|
||||||
|
|
||||||
|
// End of Packet?
|
||||||
|
if (strlen($x) == 2 and $x == "\00\00")
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Messages start with 02H 00H
|
||||||
|
if (strlen($x) == 2 AND $x != "\02\00")
|
||||||
|
throw new InvalidFidoPacketException('Not a valid packet: '.$x);
|
||||||
|
|
||||||
|
// No message attached
|
||||||
|
else if (! strlen($x))
|
||||||
|
break;
|
||||||
|
|
||||||
|
$message = new FTNMessage(fread($f,0xc));
|
||||||
|
$message->date = $this->readnullfield($f);
|
||||||
|
$message->to = $this->readnullfield($f);
|
||||||
|
$message->from = $this->readnullfield($f);
|
||||||
|
$message->subject = $this->readnullfield($f);
|
||||||
|
$message->message = $this->readnullfield($f);
|
||||||
|
|
||||||
|
$this->messages->push($message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readnullfield($f)
|
||||||
|
{
|
||||||
|
$result = '';
|
||||||
|
|
||||||
|
while (($x = fgetc($f) OR strlen($x)) AND $x !== "\00")
|
||||||
|
{
|
||||||
|
$result .= $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHeader(string $header)
|
||||||
|
{
|
||||||
|
$pack1 = [
|
||||||
|
'onode'=>[0x00,'v',2],
|
||||||
|
'dnode'=>[0x02,'v',2],
|
||||||
|
'y'=>[0x04,'v',2],
|
||||||
|
'm'=>[0x06,'v',2],
|
||||||
|
'd'=>[0x08,'v',2],
|
||||||
|
'H'=>[0x0a,'v',2],
|
||||||
|
'M'=>[0x0c,'v',2],
|
||||||
|
'S'=>[0x0e,'v',2],
|
||||||
|
'baud'=>[0x10,'v',2],
|
||||||
|
'pktver'=>[0x12,'v',2],
|
||||||
|
'onet'=>[0x14,'v',2],
|
||||||
|
'dnet'=>[0x16,'v',2],
|
||||||
|
'prodcode-lo'=>[0x18,'C',1],
|
||||||
|
'prodrev-maj'=>[0x19,'C',1],
|
||||||
|
];
|
||||||
|
|
||||||
|
$pack2 = [
|
||||||
|
'qozone'=>[0x22,'v',2],
|
||||||
|
'qdzone'=>[0x24,'v',2],
|
||||||
|
'filler'=>[0x26,'v',2],
|
||||||
|
'capvalid'=>[0x28,'v',2],
|
||||||
|
'prodcode-hi'=>[0x2a,'C',1],
|
||||||
|
'prodrev-min'=>[0x2b,'C',1],
|
||||||
|
'capword'=>[0x2c,'v',1],
|
||||||
|
'ozone'=>[0x2e,'v',2],
|
||||||
|
'dzone'=>[0x30,'v',2],
|
||||||
|
'opoint'=>[0x32,'v',2],
|
||||||
|
'dpoint'=>[0x34,'v',2],
|
||||||
|
];
|
||||||
|
|
||||||
|
$result1 = unpack($this->unpackheader($pack1),substr($header,0,0x1a));
|
||||||
|
$result2 = unpack($this->unpackheader($pack2),substr($header,0x22,0x14));
|
||||||
|
|
||||||
|
$this->pktsrc = sprintf('%s:%s/%s.%s',
|
||||||
|
array_get($result2,'ozone'),
|
||||||
|
array_get($result1,'onet'),
|
||||||
|
array_get($result1,'onode'),
|
||||||
|
array_get($result2,'dpoint')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->pktdst = sprintf('%s:%s/%s.%s',
|
||||||
|
array_get($result2,'dzone'),
|
||||||
|
array_get($result1,'dnet'),
|
||||||
|
array_get($result1,'dnode'),
|
||||||
|
array_get($result2,'dpoint')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->date = sprintf ('%d-%d-%d %d:%d:%d',
|
||||||
|
array_get($result1,'y'),
|
||||||
|
array_get($result1,'m'),
|
||||||
|
array_get($result1,'d'),
|
||||||
|
array_get($result1,'H'),
|
||||||
|
array_get($result1,'M'),
|
||||||
|
array_get($result1,'S')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->baud = array_get($result1,'baud');
|
||||||
|
$this->pktver = array_get($result1,'pktver');
|
||||||
|
|
||||||
|
$this->password = array_get(unpack('A*p',substr($header,0x1a,8)),'p');
|
||||||
|
$this->proddata = array_get(unpack('A*p',substr($header,0x36,4)),'p');
|
||||||
|
}
|
||||||
|
}
|
71
app/Console/Commands/FtnPkt.php
Normal file
71
app/Console/Commands/FtnPkt.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
use App\Classes\FTNPacket;
|
||||||
|
|
||||||
|
class FtnPkt extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'ftn:pkt {file : Fidonet Packet File PKT} {--dump : Dump packet}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Import Packet into Database';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$pkt = new FTNPacket($this->argument('file'));
|
||||||
|
|
||||||
|
$this->info(sprintf('Packet: %s has %s messages. Addr: %s->%s (Date: %s)',
|
||||||
|
$pkt->filename,
|
||||||
|
$pkt->messages->count(),
|
||||||
|
$pkt->pktsrc,
|
||||||
|
$pkt->pktdst,
|
||||||
|
$pkt->date
|
||||||
|
));
|
||||||
|
|
||||||
|
foreach ($pkt->messages as $o)
|
||||||
|
{
|
||||||
|
$this->warn(sprintf('-- From: %s(%s)->%s(%s), Type: %s, Size: %d, FQFA: %s',
|
||||||
|
$o->from,
|
||||||
|
$o->src,
|
||||||
|
$o->to,
|
||||||
|
$o->dst,
|
||||||
|
$o->description(),
|
||||||
|
strlen($o->message),
|
||||||
|
$o->fqfa
|
||||||
|
));
|
||||||
|
|
||||||
|
if ($o->unknown->count())
|
||||||
|
$this->error(sprintf('?? %s Unknown headers',$o->unknown->count()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->option('dump'))
|
||||||
|
dump($o);
|
||||||
|
}
|
||||||
|
}
|
10
app/Exceptions/InvalidFidoPacketException.php
Normal file
10
app/Exceptions/InvalidFidoPacketException.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class InvalidFidoPacketException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
68
readme.md
68
readme.md
@ -1,68 +0,0 @@
|
|||||||
<p align="center"><img src="https://laravel.com/assets/img/components/logo-laravel.svg"></p>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://travis-ci.org/laravel/framework"><img src="https://travis-ci.org/laravel/framework.svg" alt="Build Status"></a>
|
|
||||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/d/total.svg" alt="Total Downloads"></a>
|
|
||||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/v/stable.svg" alt="Latest Stable Version"></a>
|
|
||||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/license.svg" alt="License"></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## About Laravel
|
|
||||||
|
|
||||||
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel attempts to take the pain out of development by easing common tasks used in the majority of web projects, such as:
|
|
||||||
|
|
||||||
- [Simple, fast routing engine](https://laravel.com/docs/routing).
|
|
||||||
- [Powerful dependency injection container](https://laravel.com/docs/container).
|
|
||||||
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
|
|
||||||
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
|
|
||||||
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
|
|
||||||
- [Robust background job processing](https://laravel.com/docs/queues).
|
|
||||||
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
|
|
||||||
|
|
||||||
Laravel is accessible, yet powerful, providing tools needed for large, robust applications.
|
|
||||||
|
|
||||||
## Learning Laravel
|
|
||||||
|
|
||||||
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of any modern web application framework, making it a breeze to get started learning the framework.
|
|
||||||
|
|
||||||
If you're not in the mood to read, [Laracasts](https://laracasts.com) contains over 1100 video tutorials on a range of topics including Laravel, modern PHP, unit testing, JavaScript, and more. Boost the skill level of yourself and your entire team by digging into our comprehensive video library.
|
|
||||||
|
|
||||||
## Laravel Sponsors
|
|
||||||
|
|
||||||
We would like to extend our thanks to the following sponsors for helping fund on-going Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell):
|
|
||||||
|
|
||||||
- **[Vehikl](https://vehikl.com/)**
|
|
||||||
- **[Tighten Co.](https://tighten.co)**
|
|
||||||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
|
||||||
- **[64 Robots](https://64robots.com)**
|
|
||||||
- **[Cubet Techno Labs](https://cubettech.com)**
|
|
||||||
- **[British Software Development](https://www.britishsoftware.co)**
|
|
||||||
- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)**
|
|
||||||
- [UserInsights](https://userinsights.com)
|
|
||||||
- [Fragrantica](https://www.fragrantica.com)
|
|
||||||
- [SOFTonSOFA](https://softonsofa.com/)
|
|
||||||
- [User10](https://user10.com)
|
|
||||||
- [Soumettre.fr](https://soumettre.fr/)
|
|
||||||
- [CodeBrisk](https://codebrisk.com)
|
|
||||||
- [1Forge](https://1forge.com)
|
|
||||||
- [TECPRESSO](https://tecpresso.co.jp/)
|
|
||||||
- [Runtime Converter](http://runtimeconverter.com/)
|
|
||||||
- [WebL'Agence](https://weblagence.com/)
|
|
||||||
- [Invoice Ninja](https://www.invoiceninja.com)
|
|
||||||
- [iMi digital](https://www.imi-digital.de/)
|
|
||||||
- [Earthlink](https://www.earthlink.ro/)
|
|
||||||
- [Steadfast Collective](https://steadfastcollective.com/)
|
|
||||||
- [We Are The Robots Inc.](https://watr.mx/)
|
|
||||||
- [Understand.io](https://www.understand.io/)
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
|
||||||
|
|
||||||
## Security Vulnerabilities
|
|
||||||
|
|
||||||
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
|
Loading…
Reference in New Issue
Block a user