summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/VNWeb/Auth.pm2
-rw-r--r--lib/VNWeb/HTML.pm28
-rw-r--r--lib/VNWeb/User/Css.pm37
-rw-r--r--lib/VNWeb/User/Edit.pm6
-rw-r--r--sql/schema.sql3
-rw-r--r--util/updates/2022-08-25-customcss-csum.sql3
6 files changed, 52 insertions, 27 deletions
diff --git a/lib/VNWeb/Auth.pm b/lib/VNWeb/Auth.pm
index 3c0a71ba..bed2007c 100644
--- a/lib/VNWeb/Auth.pm
+++ b/lib/VNWeb/Auth.pm
@@ -83,7 +83,7 @@ for my $perm (@perms) {
# Pref(erences) are like permissions, we load these columns eagerly so they can
# be accessed through auth->pref().
my @pref_columns = qw/
- skin customcss
+ skin customcss_csum
notify_dbedit notify_post notify_comment
tags_all tags_cont tags_ero tags_tech
spoilers traits_sexual max_sexual max_violence
diff --git a/lib/VNWeb/HTML.pm b/lib/VNWeb/HTML.pm
index 68e375e6..e66b38ae 100644
--- a/lib/VNWeb/HTML.pm
+++ b/lib/VNWeb/HTML.pm
@@ -160,37 +160,17 @@ sub elm_ {
}
-
-sub _sanitize_css {
- # This function is attempting to do the impossible: Sanitize user provided
- # CSS against various attacks. I'm not expecting this to be bullet-proof.
- # Fortunately, we also have CSP in place to mitigate some problems if they
- # arise, but I'd rather not rely on it. I'd *love* to disable support for
- # external url()'s, but unfortunately many people use that to load images.
- # I'm afraid the only way to work around that is to fetch and cache those
- # URLs on the server.
- local $_ = $_[0];
- s/\\//g; # Get rid of backslashes, could be used to bypass the other regexes.
- s/@(import|charset|font-face)[^\n\;]*.//ig;
- s/javascript\s*://ig; # Not sure 'javascript:' URLs do anything, but just in case.
- s/expression\s*\(//ig; # An old IE thing I guess.
- s/binding\s*://ig; # Definitely don't want bindings.
- s/&/&/g;
- s/</&lt;/g;
- $_;
-}
-
-
sub _head_ {
my $o = shift;
my $fancy = !(auth->pref('nodistract_can') && auth->pref('nodistract_nofancy'));
my $pubskin = $fancy && $o->{dbobj} && $o->{dbobj}{id} =~ /^u/ ? tuwf->dbRowi(
- 'SELECT customcss, skin FROM users u JOIN users_prefs up ON up.id = u.id WHERE pubskin_can AND pubskin_enabled AND u.id =', \$o->{dbobj}{id}
+ 'SELECT u.id, customcss_csum, skin FROM users u JOIN users_prefs up ON up.id = u.id WHERE pubskin_can AND pubskin_enabled AND u.id =', \$o->{dbobj}{id}
) : {};
my $skin = tuwf->reqGet('skin') || $pubskin->{skin} || auth->pref('skin') || '';
$skin = config->{skin_default} if !skins->{$skin};
- my $customcss = $pubskin->{customcss} || auth->pref('customcss');
+ my $customcss = $pubskin->{customcss_csum} ? [ $pubskin->{id}, $pubskin->{customcss_csum} ] :
+ auth->pref('customcss_csum') ? [ auth->uid, auth->pref('customcss_csum') ] : undef;
meta_ charset => 'utf-8';
title_ $o->{title}.' | vndb';
@@ -198,7 +178,7 @@ sub _head_ {
link_ rel => 'shortcut icon', href => '/favicon.ico', type => 'image/x-icon';
link_ rel => 'stylesheet', href => config->{url_static}.'/g/'.$skin.'.css?'.config->{version}, type => 'text/css', media => 'all';
link_ rel => 'search', type => 'application/opensearchdescription+xml', title => 'VNDB Visual Novel Search', href => tuwf->reqBaseURI().'/opensearch.xml';
- style_ type => 'text/css', sub { lit_ _sanitize_css $customcss } if $customcss;
+ link_ rel => 'stylesheet', href => sprintf '/%s.css?%x', $customcss->[0], $customcss->[1] if $customcss;
if($o->{feeds}) {
link_ rel => 'alternate', type => 'application/atom+xml', href => "/feeds/announcements.atom", title => 'Site Announcements';
link_ rel => 'alternate', type => 'application/atom+xml', href => "/feeds/changes.atom", title => 'Recent Changes';
diff --git a/lib/VNWeb/User/Css.pm b/lib/VNWeb/User/Css.pm
new file mode 100644
index 00000000..10d21097
--- /dev/null
+++ b/lib/VNWeb/User/Css.pm
@@ -0,0 +1,37 @@
+package VNWeb::User::Css;
+
+use VNWeb::Prelude;
+
+
+sub _sanitize_css {
+ # This function is attempting to do the impossible: Sanitize user provided
+ # CSS against various attacks. I'm not expecting this to be bullet-proof.
+ # Fortunately, we also have CSP in place to mitigate some problems if they
+ # arise, but I'd rather not rely on it. I'd *love* to disable support for
+ # external url()'s, but unfortunately many people use that to load images.
+ # I'm afraid the only way to work around that is to fetch and cache those
+ # URLs on the server.
+ local $_ = $_[0];
+ s/\\//g; # Get rid of backslashes, could be used to bypass the other regexes.
+ s/@(import|charset|font-face)[^\n\;]*.//ig;
+ s/javascript\s*://ig; # Not sure 'javascript:' URLs do anything, but just in case.
+ s/expression\s*\(//ig; # An old IE thing I guess.
+ s/binding\s*://ig; # Definitely don't want bindings.
+ $_;
+}
+
+
+TUWF::get qr{/$RE{uid}\.css}, sub {
+ my $u = tuwf->dbRowi('
+ SELECT u.id, pubskin_can, pubskin_enabled, customcss
+ FROM users u
+ JOIN users_prefs up ON up.id = u.id
+ WHERE u.id =', \tuwf->capture('id'));
+ return tuwf->resNotFound if !$u->{id};
+ return tuwf->resDenied if !($u->{pubskin_can} && $u->{pubskin_enabled}) && !(auth && auth->uid eq $u->{id});
+ tuwf->resHeader('Content-type', 'text/css; charset=UTF8');
+ tuwf->resHeader('Cache-Control', 'max-age=31536000'); # invalidation is done by adding a checksum to the URL.
+ lit_ _sanitize_css $u->{customcss};
+};
+
+1;
diff --git a/lib/VNWeb/User/Edit.pm b/lib/VNWeb/User/Edit.pm
index c33f04e7..c676a534 100644
--- a/lib/VNWeb/User/Edit.pm
+++ b/lib/VNWeb/User/Edit.pm
@@ -4,6 +4,9 @@ use VNWeb::Prelude;
use VNDB::Skins;
use VNWeb::LangPref;
+use Digest::SHA 'sha1';
+use Encode 'encode_utf8';
+
my $FORM = {
id => { vndbid => 'u' },
@@ -48,7 +51,7 @@ my $FORM = {
staffed_olang => { anybool => 1 },
staffed_unoff => { anybool => 1 },
skin => { enum => skins },
- customcss => { required => 0, default => '', maxlength => 2000 },
+ customcss => { required => 0, default => '', maxlength => 16*1024 },
traits => { sort_keys => 'tid', maxlength => 100, aoh => {
tid => { vndbid => 'i' },
@@ -155,6 +158,7 @@ elm_api UserEdit => $FORM_OUT, $FORM_IN, sub {
vnrel_langs vnrel_olang vnrel_mtl staffed_langs staffed_olang staffed_unoff
spoilers skin customcss title_langs alttitle_langs
/;
+ $setp{customcss_csum} = length $p->{customcss} ? unpack 'q', sha1 encode_utf8 $p->{customcss} : 0;
tuwf->dbExeci('DELETE FROM users_traits WHERE id =', \$data->{id});
tuwf->dbExeci('INSERT INTO users_traits', { id => $data->{id}, tid => $_->{tid} }) for $p->{traits}->@*;
}
diff --git a/sql/schema.sql b/sql/schema.sql
index e6fc7874..1d2ac7e5 100644
--- a/sql/schema.sql
+++ b/sql/schema.sql
@@ -1094,7 +1094,8 @@ CREATE TABLE users_prefs (
vnrel_mtl boolean NOT NULL DEFAULT false,
staffed_langs language[],
staffed_olang boolean NOT NULL DEFAULT true,
- staffed_unoff boolean NOT NULL DEFAULT false
+ staffed_unoff boolean NOT NULL DEFAULT false,
+ customcss_csum bigint NOT NULL DEFAULT 0
);
-- Additional fields for the 'users' table, but with some protected columns.
diff --git a/util/updates/2022-08-25-customcss-csum.sql b/util/updates/2022-08-25-customcss-csum.sql
new file mode 100644
index 00000000..8a2a8938
--- /dev/null
+++ b/util/updates/2022-08-25-customcss-csum.sql
@@ -0,0 +1,3 @@
+ALTER TABLE users_prefs ADD COLUMN customcss_csum bigint NOT NULL DEFAULT 0;
+-- '1' is not exactly a checksum, but it'll do fine for the first version.
+UPDATE users_prefs SET customcss_csum = 1 WHERE customcss <> '';