<commit_before>package Mojolicious::Static;
use Mojo::Base -base;

use File::Spec::Functions 'catfile';
use Mojo::Asset::File;
use Mojo::Asset::Memory;
use Mojo::Date;
use Mojo::Home;
use Mojo::Loader;
use Mojo::Util 'md5_sum';

has classes => sub { ['main'] };
has paths   => sub { [] };

# Last modified default
my $MTIME = time;

# Bundled files
my $HOME   = Mojo::Home->new;
my $PUBLIC = $HOME->parse($HOME->mojo_lib_dir)->rel_dir('Mojolicious/public');

sub dispatch {
  my ($self, $c) = @_;

  # Canonical path
  my $stash = $c->stash;
  my $path  = $c->req->url->path;
  $path = $stash->{path} ? $path->new($stash->{path}) : $path->clone;
  return undef unless my @parts = @{$path->canonicalize->parts};

  # Serve static file and prevent directory traversal
  return undef if $parts[0] eq '..' || !$self->serve($c, join('/', @parts));
  $stash->{'mojo.static'}++;
  return !!$c->rendered;
}

sub file {
  my ($self, $rel) = @_;

  # Search all paths
  for my $path (@{$self->paths}) {
    next unless my $asset = $self->_get_file(catfile $path, split('/', $rel));
    return $asset;
  }

  # Search DATA
  if (my $asset = $self->_get_data_file($rel)) { return $asset }

  # Search bundled files
  return $self->_get_file(catfile($PUBLIC, split('/', $rel)));
}

sub is_fresh {
  my ($self, $c) = @_;

  # Unconditional
  my $req_headers = $c->req->headers;
  my $match       = $req_headers->if_none_match;
  my $since       = $req_headers->if_modified_since;
  return undef unless $match || $since;

  # If-None-Match
  my ($matches, $unmodified) = (1, 1);
  my $res_headers = $c->res->headers;
  $matches = undef if $match && ($res_headers->etag // '') ne $match;

  # If-Modified-Since
  my $last = Mojo::Date->new($res_headers->last_modified // '')->epoch;
  $unmodified = !$since || $last <= (Mojo::Date->new($since)->epoch // 0);

  return $matches && $unmodified;
}

sub serve {
  my ($self, $c, $rel) = @_;
  return undef unless my $asset = $self->file($rel);
  my $types = $c->app->types;
  my $type = $rel =~ /\.(\w+)$/ ? $types->type($1) : undef;
  $c->res->headers->content_type($type || $types->type('txt'));
  return !!$self->serve_asset($c, $asset);<commit_msg>small optimizations<commit_after>package Mojolicious::Static;
use Mojo::Base -base;

use File::Spec::Functions 'catfile';
use Mojo::Asset::File;
use Mojo::Asset::Memory;
use Mojo::Date;
use Mojo::Home;
use Mojo::Loader;
use Mojo::Util 'md5_sum';

has classes => sub { ['main'] };
has paths   => sub { [] };

# Last modified default
my $MTIME = time;

# Bundled files
my $HOME   = Mojo::Home->new;
my $PUBLIC = $HOME->parse($HOME->mojo_lib_dir)->rel_dir('Mojolicious/public');

sub dispatch {
  my ($self, $c) = @_;

  # Canonical path
  my $stash = $c->stash;
  my $path  = $c->req->url->path;
  $path = $stash->{path} ? $path->new($stash->{path}) : $path->clone;
  return undef unless my @parts = @{$path->canonicalize->parts};

  # Serve static file and prevent directory traversal
  return undef if $parts[0] eq '..' || !$self->serve($c, join('/', @parts));
  $stash->{'mojo.static'}++;
  return !!$c->rendered;
}

sub file {
  my ($self, $rel) = @_;

  # Search all paths
  for my $path (@{$self->paths}) {
    next unless my $asset = $self->_get_file(catfile $path, split('/', $rel));
    return $asset;
  }

  # Search DATA
  if (my $asset = $self->_get_data_file($rel)) { return $asset }

  # Search bundled files
  return $self->_get_file(catfile($PUBLIC, split('/', $rel)));
}

sub is_fresh {
  my ($self, $c) = @_;

  # Unconditional
  my $req_headers = $c->req->headers;
  my $match       = $req_headers->if_none_match;
  return undef unless (my $since = $req_headers->if_modified_since) || $match;

  # If-None-Match
  my $res_headers = $c->res->headers;
  my $matches = $match && ($res_headers->etag // '') ne $match ? 0 : 1;

  # If-Modified-Since
  my $last = Mojo::Date->new($res_headers->last_modified // '')->epoch;
  my $unmodified = !$since || $last <= (Mojo::Date->new($since)->epoch // 0);

  return $matches && $unmodified;
}

sub serve {
  my ($self, $c, $rel) = @_;
  return undef unless my $asset = $self->file($rel);
  my $types = $c->app->types;
  my $type = $rel =~ /\.(\w+)$/ ? $types->type($1) : undef;
  $c->res->headers->content_type($type || $types->type('txt'));
  return !!$self->serve_asset($c, $asset);
{'fraction_non_alphanumeric': 0.21471540953262377, 'fraction_numerical': 0.002776492364645997, 'mean_word_length': 3.862767154105737, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 91, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6311995', 'n_tokens_mistral': 1641, 'n_tokens_neox': 1592, 'n_words': 386}