#!/usr/bin/perl # # app.psgi # # Dreamwidth entrypoint for Plack. # # Authors: # Mark Smith # # Copyright (c) 2021 by Dreamwidth Studios, LLC. # # This program is free software; you may redistribute it and/or modify it under # the same terms as Perl itself. For a copy of the license, please reference # 'perldoc perlartistic' or 'perldoc perlgpl'. # use strict; use v5.10; use Log::Log4perl; my $log = Log::Log4perl->get_logger(__PACKAGE__); BEGIN { require "$ENV{LJHOME}/cgi-bin/ljlib.pl"; } use Plack::Builder; use DW::BML; use DW::Controller::Journal; use DW::Request::Plack; use DW::Routing; # Initial configuration that happens on server startup goes here, these things don't # change from request to request, so only put things here that never change BEGIN { # If we're a DEV server, do some configuration $LJ::IS_DEV_SERVER = 1 if $ENV{LJ_IS_DEV_SERVER}; $^W = 1 if $LJ::IS_DEV_SERVER; # Configure S2 S2::set_domain('LJ'); # Initialize random srand( LJ::urandom_int() ); } my $app = sub { my $env = $_[0]; my $r = DW::Request->get; # Main request dispatch; this will determine what kind of request we're getting # and then pass it to the appropriate handler. In the future, this should just # be a call to DW::Routing and let it sort it out with all the controllers and # such, but until then, we're having to dispatch between various generations # of systems ourselves. # If this is the embed module domain, force routing to embedcontent handler # regardless of the requested path (matches Apache::LiveJournal::trans behavior) my $host = $r->host; my $uri = ( $LJ::EMBED_MODULE_DOMAIN && $host =~ /$LJ::EMBED_MODULE_DOMAIN$/ ) ? '/journal/embedcontent' : $r->path; # Handle legacy RPC URIs (/__rpc_delcomment, /__rpc_talkscreen) that # Apache routes via LJ::URI->handle() to BML files if ( my ($rpc) = $uri =~ m!^.*/__rpc_(\w+)$! ) { if ( my $bml_file = $LJ::AJAX_URI_MAP{$rpc} ) { DW::BML->render( "$LJ::HTDOCS/$bml_file", $uri ); return $r->res; } } my $ret = DW::Routing->call( uri => $uri, username => $env->{'dw.journal_user'}, ); # If routing returned a finalized Plack response (arrayref), e.g. from # a controller redirect, return it directly — it already has cookies/headers set. return $ret if ref $ret; # If routing returned OK (0), default status to 200; otherwise try journals, then BML if ( defined $ret && $ret == 0 ) { $r->status(200) unless $r->status; } else { # Journal routing: subdomain-based (set by SubdomainFunction middleware) # or path-based (/~user/... and /users/user/...) unless ( defined $ret ) { my ( $journal_user, $journal_path ); if ( $env->{'dw.journal_user'} ) { $journal_user = $env->{'dw.journal_user'}; $journal_path = $env->{'dw.journal_path'} || '/'; } elsif ( $uri =~ m!^/(?:~|users/)([\w-]+)(.*)$! ) { ( $journal_user, $journal_path ) = ( $1, $2 || '/' ); } if ($journal_user) { $ret = DW::Controller::Journal->render( user => $journal_user, uri => $journal_path, args => $r->query_string, ); } } # If journal routing handled it, finalize if ( ref $ret ) { return $ret; } elsif ( defined $ret && $ret == 0 ) { $r->status(200) unless $r->status; } elsif ( defined $ret && $ret > 0 ) { $r->status($ret); } else { # Routing didn't handle it — try BML file resolution as fallback my ( $redirect_url, $bml_uri, $bml_file ) = DW::BML->resolve_path($uri); if ($redirect_url) { return $r->redirect($redirect_url); } elsif ($bml_file) { DW::BML->render( $bml_file, $bml_uri ); } else { $r->status(404) unless $r->status; } } } return $r->res; }; # Apply the middleware. Ordering is important! builder { # Handle OPTIONs requests and otherwise only allow the methods that we expect # to be allowed; this will abort any calls that are methods that not accepted enable 'Options', allowed => [qw /DELETE GET HEAD POST PUT/]; # Security headers on all responses (matches Apache::LiveJournal::trans) enable 'DW::SecurityHeaders'; # Manages start/end request and things we might want to do around the entire # request lifecycle such as logging, resource checking, etc enable 'DW::RequestWrapper'; # Middleware for doing domain redirect management, i.e., we want to ensure that the # user has ended up on the right domain (www.dreamwidth.org instead of dreamwidth.co.uk # and the like), is also responsible for managing redirect.dat etc enable 'DW::Redirects'; # Handle functional subdomains (shop.dw.org, support.dw.org, etc) by redirecting # or rewriting URIs to match Apache::LiveJournal::trans behavior enable 'DW::SubdomainFunction'; if ($LJ::IS_DEV_SERVER) { enable 'DW::Dev'; } # Ensure that we get the real user's IP address instead of a proxy enable 'DW::XForwardedFor'; # Concatenated static resources (CSS/JS combo handler) enable 'DW::ConcatRes'; # Plain static file serving from all htdocs directories (main + extensions like # dw-nonfree). Ordered by scope priority so extension files override base files. # pass_through lets each layer fall through to the next if the file isn't found. for my $dir ( LJ::get_all_directories('htdocs') ) { enable 'Static', path => qr{^/(img|stc|js)/}, root => $dir, pass_through => 1; } # Middleware for ensuring we have the Unique Cookie set up enable 'DW::UniqCookie'; # Middleware for doing user authentication (get remote, dev server ?as= support) enable 'DW::Auth'; # Middleware for doing sysban blocking (IP bans, uniq bans, tempbans, noanon_ip) enable 'DW::Sysban'; # Rate limiting (after auth and sysban, before request dispatch) enable 'DW::RateLimit'; $app; };