| 1 | #!/usr/bin/perl |
|---|
| 2 | # |
|---|
| 3 | |
|---|
| 4 | package Apache::LiveJournal; |
|---|
| 5 | |
|---|
| 6 | use strict; |
|---|
| 7 | use Apache::Constants qw(:common REDIRECT HTTP_NOT_MODIFIED); |
|---|
| 8 | use Apache::File (); |
|---|
| 9 | use CGI; |
|---|
| 10 | |
|---|
| 11 | my %RQ; # per-request data |
|---|
| 12 | my %USERPIC; # conf related to userpics |
|---|
| 13 | |
|---|
| 14 | # init handler. |
|---|
| 15 | sub handler |
|---|
| 16 | { |
|---|
| 17 | my $r = shift; |
|---|
| 18 | |
|---|
| 19 | $r->set_handlers(PerlTransHandler => [ \&trans ]); |
|---|
| 20 | $r->push_handlers(PerlCleanupHandler => sub { %RQ = (); }); |
|---|
| 21 | |
|---|
| 22 | $USERPIC{'cache_dir'} = "$ENV{'LJHOME'}/htdocs/userpics"; |
|---|
| 23 | $USERPIC{'use_disk_cache'} = -d $USERPIC{'cache_dir'}; |
|---|
| 24 | |
|---|
| 25 | return OK; |
|---|
| 26 | } |
|---|
| 27 | |
|---|
| 28 | sub trans |
|---|
| 29 | { |
|---|
| 30 | my $r = shift; |
|---|
| 31 | my $uri = $r->uri; |
|---|
| 32 | my $host = $r->header_in("Host"); |
|---|
| 33 | |
|---|
| 34 | LJ::start_request(); |
|---|
| 35 | |
|---|
| 36 | return trans_userpic($r, $1) if $uri =~ m!^/userpic/(\d+)$!; |
|---|
| 37 | |
|---|
| 38 | my $redir = sub { |
|---|
| 39 | my $url = shift; |
|---|
| 40 | $r->content_type("text/html"); |
|---|
| 41 | $r->header_out(Location => $url); |
|---|
| 42 | return REDIRECT; |
|---|
| 43 | }; |
|---|
| 44 | |
|---|
| 45 | my $journal_view = sub { |
|---|
| 46 | my $opts = shift; |
|---|
| 47 | $opts ||= {}; |
|---|
| 48 | |
|---|
| 49 | if ($opts->{'user'} ne lc($opts->{'user'})) { |
|---|
| 50 | my $url = LJ::journal_base(lc($opts->{'user'}), $opts->{'vhost'}) . |
|---|
| 51 | "/$opts->{'mode'}$opts->{'args'}"; |
|---|
| 52 | return $redir->($url); |
|---|
| 53 | } |
|---|
| 54 | |
|---|
| 55 | $opts->{'user'} = LJ::canonical_username($opts->{'user'}); |
|---|
| 56 | |
|---|
| 57 | if ($opts->{'mode'} eq "info") { |
|---|
| 58 | return $redir->("$LJ::SITEROOT/userinfo.bml?user=$opts->{'user'}"); |
|---|
| 59 | } |
|---|
| 60 | |
|---|
| 61 | %RQ = %$opts; |
|---|
| 62 | $r->handler("perl-script"); |
|---|
| 63 | $r->push_handlers(PerlHandler => \&journal_content); |
|---|
| 64 | return OK; |
|---|
| 65 | }; |
|---|
| 66 | |
|---|
| 67 | if ($LJ::USER_VHOSTS && |
|---|
| 68 | $host =~ /^([\w\-]{1,15})\.\Q$LJ::USER_DOMAIN\E(:\d+)?$/ && |
|---|
| 69 | $1 ne "www") |
|---|
| 70 | { |
|---|
| 71 | my $user = $1; |
|---|
| 72 | return $journal_view->({'vhost' => 'users', |
|---|
| 73 | 'mode' => $1, |
|---|
| 74 | 'args' => $2, |
|---|
| 75 | 'user' => $user, }) |
|---|
| 76 | if $uri =~ m!/(\w+)?([^\?]*)!; |
|---|
| 77 | return $journal_view->(undef); # undef |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | if ($LJ::DOMAIN_PREPEND_WWW && |
|---|
| 81 | $host =~ /^\Q$LJ::DOMAIN\E(:\d+)?$/) |
|---|
| 82 | { |
|---|
| 83 | $r->content_type("text/html"); |
|---|
| 84 | $r->header_out(Location => "$LJ::SITEROOT$uri"); |
|---|
| 85 | return REDIRECT; |
|---|
| 86 | } |
|---|
| 87 | |
|---|
| 88 | # normal (non-domain) journal view |
|---|
| 89 | if ($uri =~ m! |
|---|
| 90 | ^/(users\/|community\/|\~) # users/community/tilde |
|---|
| 91 | (\w{1,15}) # mandatory username |
|---|
| 92 | (?:/(\w+)?)? # optional /<viewname> |
|---|
| 93 | ([^\?]*) # extra args |
|---|
| 94 | !x && ($3 eq "" || defined $LJ::viewinfo{$3})) |
|---|
| 95 | { |
|---|
| 96 | my $vhost = { 'users/' => '', 'community/' => 'community', |
|---|
| 97 | '~' => 'tilde' }->{$1}; |
|---|
| 98 | return $journal_view->({'vhost' => $vhost, |
|---|
| 99 | 'mode' => $3, |
|---|
| 100 | 'args' => $4, |
|---|
| 101 | 'user' => $2, }); |
|---|
| 102 | } |
|---|
| 103 | |
|---|
| 104 | return FORBIDDEN if $uri =~ m!^/userpics!; |
|---|
| 105 | return DECLINED; |
|---|
| 106 | } |
|---|
| 107 | |
|---|
| 108 | sub trans_userpic |
|---|
| 109 | { |
|---|
| 110 | my $r = shift; |
|---|
| 111 | my $picid = shift; |
|---|
| 112 | |
|---|
| 113 | # we can safely do this without checking since we never re-use |
|---|
| 114 | # picture IDs and don't let the contents get modified |
|---|
| 115 | return HTTP_NOT_MODIFIED if $r->header_in('If-Modified-Since'); |
|---|
| 116 | |
|---|
| 117 | $RQ{'picid'} = $picid; |
|---|
| 118 | |
|---|
| 119 | my @dirs_make; |
|---|
| 120 | my $file; |
|---|
| 121 | if ($picid =~ /^\d*(\d\d)(\d\d\d)$/) { |
|---|
| 122 | push @dirs_make, ("$USERPIC{'cache_dir'}/$2", |
|---|
| 123 | "$USERPIC{'cache_dir'}/$2/$1"); |
|---|
| 124 | $file = "$USERPIC{'cache_dir'}/$2/$1/$picid"; |
|---|
| 125 | } else { |
|---|
| 126 | my $mod = sprintf("%03d", $picid % 1000); |
|---|
| 127 | push @dirs_make, "$USERPIC{'cache_dir'}/$mod"; |
|---|
| 128 | $file = "$USERPIC{'cache_dir'}/$mod/p$picid"; |
|---|
| 129 | } |
|---|
| 130 | |
|---|
| 131 | if ($USERPIC{'use_disk_cache'}) { |
|---|
| 132 | foreach (@dirs_make) { |
|---|
| 133 | next if -d $_; |
|---|
| 134 | mkdir $_, 0777; |
|---|
| 135 | } |
|---|
| 136 | } |
|---|
| 137 | |
|---|
| 138 | # set both, so we can compared later if they're the same, |
|---|
| 139 | # and thus know if directories were created (if not, |
|---|
| 140 | # apache will give us a pathinfo) |
|---|
| 141 | $RQ{'userpicfile'} = $file; |
|---|
| 142 | $r->filename($file); |
|---|
| 143 | |
|---|
| 144 | $r->handler("perl-script"); |
|---|
| 145 | $r->push_handlers(PerlHandler => \&content_userpic); |
|---|
| 146 | return OK; |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | sub content_userpic |
|---|
| 150 | { |
|---|
| 151 | my $r = shift; |
|---|
| 152 | my $file = $r->filename; |
|---|
| 153 | |
|---|
| 154 | my $picid = $RQ{'picid'}; |
|---|
| 155 | |
|---|
| 156 | # will we try to use disk cache? |
|---|
| 157 | my $disk_cache = $USERPIC{'use_disk_cache'} && |
|---|
| 158 | $file eq $RQ{'userpicfile'}; |
|---|
| 159 | |
|---|
| 160 | my ($pic, $data, $lastmod); |
|---|
| 161 | my $need_cache; |
|---|
| 162 | |
|---|
| 163 | my $mime = "image/jpeg"; |
|---|
| 164 | my $set_mime = sub { |
|---|
| 165 | my $data = shift; |
|---|
| 166 | if ($data =~ /^GIF/) { $mime = "image/gif"; } |
|---|
| 167 | elsif ($data =~ /^\x89PNG/) { $mime = "image/png"; } |
|---|
| 168 | }; |
|---|
| 169 | my $size; |
|---|
| 170 | |
|---|
| 171 | my $send_headers = sub { |
|---|
| 172 | $r->content_type($mime); |
|---|
| 173 | $r->header_out("Content-length", $size); |
|---|
| 174 | $r->header_out("Expires", LJ::time_to_http(time()+3000000)); |
|---|
| 175 | $r->header_out("Cache-Control", "no-transform"); |
|---|
| 176 | $r->header_out("Last-Modified", LJ::time_to_http($lastmod)); |
|---|
| 177 | $r->send_http_header(); |
|---|
| 178 | }; |
|---|
| 179 | |
|---|
| 180 | # try to get it from disk if in disk-cache mode |
|---|
| 181 | if ($disk_cache) { |
|---|
| 182 | if (-s $r->finfo) { |
|---|
| 183 | $lastmod = (stat _)[9]; |
|---|
| 184 | $size = -s _; |
|---|
| 185 | my $fh = Apache::File->new($file); |
|---|
| 186 | my $magic; |
|---|
| 187 | read($fh, $magic, 4); |
|---|
| 188 | $set_mime->($magic); |
|---|
| 189 | $send_headers->(); |
|---|
| 190 | $r->print($magic); |
|---|
| 191 | $r->send_fd($fh); |
|---|
| 192 | $fh->close(); |
|---|
| 193 | return OK; |
|---|
| 194 | } else { |
|---|
| 195 | $need_cache = 1; |
|---|
| 196 | } |
|---|
| 197 | } |
|---|
| 198 | |
|---|
| 199 | # else, get it from db. |
|---|
| 200 | unless ($data) { |
|---|
| 201 | my $dbr = LJ::get_dbh("slave", "master"); |
|---|
| 202 | my $query = "SELECT p.state, p.userid, p.contenttype, UNIX_TIMESTAMP(p.picdate) ". |
|---|
| 203 | "AS 'lastmod', u.clusterid, u.dversion FROM userpic p, user u WHERE ". |
|---|
| 204 | "p.picid=$picid AND u.userid=p.userid"; |
|---|
| 205 | $pic = $dbr->selectrow_hashref($query); |
|---|
| 206 | return NOT_FOUND unless $pic; |
|---|
| 207 | |
|---|
| 208 | $lastmod = $pic->{'lastmod'}; |
|---|
| 209 | if ($pic->{'dversion'} >= 2) { |
|---|
| 210 | my $dbb = LJ::get_cluster_reader($pic->{'clusterid'}); |
|---|
| 211 | return SERVER_ERROR unless $dbb; |
|---|
| 212 | $data = $dbb->selectrow_array("SELECT imagedata FROM userpicblob2 WHERE ". |
|---|
| 213 | "userid=$pic->{'userid'} AND picid=$picid"); |
|---|
| 214 | } else { |
|---|
| 215 | $data = $dbr->selectrow_array("SELECT imagedata FROM userpicblob WHERE ". |
|---|
| 216 | "picid=$picid"); |
|---|
| 217 | } |
|---|
| 218 | } |
|---|
| 219 | |
|---|
| 220 | return NOT_FOUND unless $data; |
|---|
| 221 | |
|---|
| 222 | if ($need_cache && open (F, ">$file")) { |
|---|
| 223 | print F $data; |
|---|
| 224 | close F; |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | $set_mime->($data); |
|---|
| 228 | $size = length($data); |
|---|
| 229 | $send_headers->(); |
|---|
| 230 | $r->print($data); |
|---|
| 231 | return OK; |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | sub content |
|---|
| 235 | { |
|---|
| 236 | my $r = shift; |
|---|
| 237 | my $uri = $r->uri; |
|---|
| 238 | return DECLINED if $uri =~ /dev/; |
|---|
| 239 | |
|---|
| 240 | $r->content_type("text/html; charset=utf-8"); |
|---|
| 241 | $r->send_http_header(); |
|---|
| 242 | $r->print("$uri; " . $r->header_in("Host")); |
|---|
| 243 | |
|---|
| 244 | return OK; |
|---|
| 245 | |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | sub journal_content |
|---|
| 249 | { |
|---|
| 250 | my $r = shift; |
|---|
| 251 | my $uri = $r->uri; |
|---|
| 252 | |
|---|
| 253 | my $dbs = LJ::get_dbs(); |
|---|
| 254 | |
|---|
| 255 | if ($RQ{'vhost'} eq "users" && |
|---|
| 256 | $uri eq "/robots.txt") |
|---|
| 257 | { |
|---|
| 258 | my $u = { 'user' => $RQ{'user'} }; |
|---|
| 259 | LJ::load_user_props($dbs, $u, "opt_blockrobots"); |
|---|
| 260 | $r->content_type("text/plain"); |
|---|
| 261 | $r->send_http_header(); |
|---|
| 262 | $r->print("User-Agent: *\n"); |
|---|
| 263 | if ($u->{'opt_blockrobots'}) { |
|---|
| 264 | $r->print("Disallow: /\n"); |
|---|
| 265 | } |
|---|
| 266 | return OK; |
|---|
| 267 | } |
|---|
| 268 | |
|---|
| 269 | my $cgi = new CGI(); |
|---|
| 270 | my $criterr = 0; |
|---|
| 271 | my $remote = LJ::get_remote($dbs, \$criterr, $cgi); |
|---|
| 272 | |
|---|
| 273 | # check for faked cookies here, since this is pretty central. |
|---|
| 274 | if ($criterr) { |
|---|
| 275 | $r->content_type("text/html"); |
|---|
| 276 | $r->send_http_header(); |
|---|
| 277 | $r->print("Invalid cookies. Try <a href='$LJ::SITEROOT/logout.bml'>logging out</a> and then logging back in.\n"); |
|---|
| 278 | return OK; |
|---|
| 279 | } |
|---|
| 280 | |
|---|
| 281 | my %headers = (); |
|---|
| 282 | my $opts = { |
|---|
| 283 | 'headers' => \%headers, |
|---|
| 284 | 'args' => $RQ{'args'}, |
|---|
| 285 | 'vhost' => $RQ{'vhost'}, |
|---|
| 286 | 'env' => \%ENV, |
|---|
| 287 | }; |
|---|
| 288 | |
|---|
| 289 | my $user = $RQ{'user'}; |
|---|
| 290 | my $html = LJ::make_journal($dbs, $user, $RQ{'mode'}, |
|---|
| 291 | $remote, $opts); |
|---|
| 292 | |
|---|
| 293 | my $status = $opts->{'status'} || "200 OK"; |
|---|
| 294 | unless ($opts->{'contenttype'}) { |
|---|
| 295 | $opts->{'contenttype'} = "text/html"; |
|---|
| 296 | if ($LJ::UNICODE) { |
|---|
| 297 | $opts->{'contenttype'} .= "; charset=utf-8"; |
|---|
| 298 | } |
|---|
| 299 | } |
|---|
| 300 | |
|---|
| 301 | if ($opts->{'badargs'}) |
|---|
| 302 | { |
|---|
| 303 | $status = "404 Not Found"; |
|---|
| 304 | $html = "<H1>Not Found</H1>Unknown page or arguments."; |
|---|
| 305 | } |
|---|
| 306 | elsif ($opts->{'baduser'}) |
|---|
| 307 | { |
|---|
| 308 | $status = "404 Unknown User"; |
|---|
| 309 | $html = "<H1>Unknown User</H1>There is no user <b>$user</b> at $LJ::SITENAME."; |
|---|
| 310 | } |
|---|
| 311 | |
|---|
| 312 | unless ($html) { |
|---|
| 313 | $html = "<h1>Error</h1><p>User <b>$user</b> has messed up their journal template definition.</p>"; |
|---|
| 314 | } |
|---|
| 315 | |
|---|
| 316 | $r->status_line($status); |
|---|
| 317 | foreach my $hname (keys %headers) { |
|---|
| 318 | if (ref($headers{$hname}) && ref($headers{$hname}) eq "ARRAY") { |
|---|
| 319 | foreach (@{$headers{$hname}}) { |
|---|
| 320 | $r->header_out($hname, $_); |
|---|
| 321 | } |
|---|
| 322 | } else { |
|---|
| 323 | $r->header_out($hname, $headers{$hname}); |
|---|
| 324 | } |
|---|
| 325 | } |
|---|
| 326 | |
|---|
| 327 | if ($opts->{'nocontent'}) { |
|---|
| 328 | $r->send_http_header(); |
|---|
| 329 | return OK; |
|---|
| 330 | } |
|---|
| 331 | |
|---|
| 332 | $r->header_out("Content-type", $opts->{'contenttype'}); |
|---|
| 333 | $r->header_out("Cache-Control", "private, proxy-revalidate"); |
|---|
| 334 | $r->header_out("Vary", "Accept-Encoding, Cookie"); |
|---|
| 335 | $r->header_out("Content-length", length($html)); |
|---|
| 336 | $r->send_http_header(); |
|---|
| 337 | $r->print($html); |
|---|
| 338 | |
|---|
| 339 | return OK; |
|---|
| 340 | |
|---|
| 341 | } |
|---|
| 342 | |
|---|
| 343 | 1; |
|---|