diff options
-rw-r--r-- | lib/VNWeb/Auth.pm | 2 | ||||
-rw-r--r-- | lib/VNWeb/HTML.pm | 28 | ||||
-rw-r--r-- | lib/VNWeb/User/Css.pm | 37 | ||||
-rw-r--r-- | lib/VNWeb/User/Edit.pm | 6 | ||||
-rw-r--r-- | sql/schema.sql | 3 | ||||
-rw-r--r-- | util/updates/2022-08-25-customcss-csum.sql | 3 |
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/</</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 <> ''; |