From 785ad3685f2f393b2a9dc3d1fe5be4525eaacafb Mon Sep 17 00:00:00 2001 From: Ricky Elrod Date: Thu, 8 Jun 2017 19:21:39 +0000 Subject: [PATCH] [modernpaste] Update rewrite rules for puiterwijk patch, document the crap out of them Signed-off-by: Ricky Elrod --- roles/modernpaste/files/modern-paste.conf | 114 ++++++++++++++++++++-- 1 file changed, 107 insertions(+), 7 deletions(-) diff --git a/roles/modernpaste/files/modern-paste.conf b/roles/modernpaste/files/modern-paste.conf index a9fc0feea5..3a7e321083 100644 --- a/roles/modernpaste/files/modern-paste.conf +++ b/roles/modernpaste/files/modern-paste.conf @@ -2,7 +2,112 @@ WSGIDaemonProcess stickynotes2modernpaste user=apache group=apache threads=5 WSGIScriptAlias /stickynotes2modernpaste /usr/share/stickynotes2modernpaste/stickynotes2modernpaste.wsgi WSGISocketPrefix run/wsgi +# Grab a cup of coffee, a light snack, and turn on some classical music. +# You're in for a bit of a novel. +# +# The below rules are worthy of some comment so that later on when I (or +# heaven forbid anyone else) have to revisit them for some horrible reason, +# they can be referred to and maybe (but unlikely) useful. +# +# Chapter 1. Background. +# +# The rewrite rules exist solely for the purpose of continuing to support old +# `fpaste` (the CLI app). This is in the process of being rewritten, and one +# day we won't have to support it anymore. But for now, we do, because it's on +# live media (and, I believe, Desktop installs, by default), and when a user is +# having issues and asking for help in IRC, they need to be able to use +# `fpaste` to do so. So, that is why we care about `fpaste` in its current +# (F25-F26) form. +# +# You see, fpaste was written in such a way that it makes a lot of weird +# assumptions that don't hold anymore. I will not speculate on why it was +# written the way it was, but I _will_ briefly outline some of the intricacies +# of supporting it. +# +# First off the workflow is something like this: +# 1. User wants to paste some text. Who knows why they want to do this. Maybe +# they are bored and want to see how broken our rewrite rules are. Maybe +# they hate me and want to see me cry trying to fix them. Who knows?! +# +# 2. The fpaste client makes a POST on their behalf to /. This POST payload +# includes the text of the paste and some other information (paste +# language, etc). +# +# 3. The server sees the POST, matches it against our rules below, and +# decides that it needs to redirect them to stickynotes2modernpaste, a +# custom Flask app that I (relrod) wrote so that we could handle requests +# that are in the form our old stickynotes pastebin accepted, and proxy +# them to modernpaste. +# +# Note that this matches the first set of crazy RewriteConds below. We +# only want to send CLI users there, and only when they POST to /. At +# this point, at least. +# +# 4. sn2mp says "okay cool," proxies the paste to modernpaste via its JSON +# API, and returns back to fpaste a JSON blob that contains JSON with two +# keys that fpaste requires exists. In our response, one of them is always +# an empty string, and the other is the id of the paste, prefixed with +# "paste/". +# +# 5. At this point, fpaste has enough information to return a URL to the +# paste. However, things are not all okay in the world. You see, fpaste +# wants to show a short-url too. Apparently people don't like typing or +# something. To generate a short-url, the fpaste client sends another POST +# to us, at the path "/paste/[the paste id]//". In the past, when it would +# do this, stickynotes would return a JSON blob that included the +# short-url. In fact, it would always include the short-url at the +# third-line from the last in its JSON response, and the fpaste client +# hardcoded that assumption. See Chapter 2 for information about the "//". +# +# 6. When we get this second POST, we again send the client to sn2mp. We add +# a few more crazy RewriteConds to ensure that we only add this behavior +# for fpaste and not most users. We know paste IDs are 22-24 characters +# long (as per https://github.com/LINKIWI/modern-paste/pull/33) and that +# the client will always POST to "/paste/[the paste id]//". So we match on +# that. If we match, sn2mp will take everything after its name and append +# it to the URL that ultimately gets shipped to da.gd for shortening. Then +# it returns a (malformed) JSON blob that is written in exactly the way +# fpaste expects. +# +# 7. Then fpaste shows the user both URLs, and all is okay. +# +# Chapter 2. Trailing slashes. +# +# The fpaste client defaults to private mode, but modernpaste doesn't support +# that, per se. You can password-protect pastes, but that's about it. +# +# However, they way stickynotes worked, it used /[paste id]/[secret] when a +# paste was private. Since modernpaste does things differently, sn2mp never +# returns the [secret] part of that URL. Or rather, it returns the empty string +# in its place. This means, by default (private mode = true), fpaste will +# both render, and internally use, a URL that has /[paste id]/[secret]. But +# since [secret] is the empty string, this is equivalent to /[paste id]/, with +# the trailing slash. +# +# To make matters worse, this little gem is found in the fpaste procedure for +# doing the second POST (#5 and 6 above): +# +# eq = urllib.request.Request(url=long_url+'/', data=params.encode()) +# +# Yep, it adds a '/' for the second POST. So we get POSTs to +# /paste/[paste id]// during the second POST. +# +# Our capture of the paste ID below (the ".{22,24}" part) will match the first +# trailing slash, but not the second (because of the /$ that comes after). +# Nevertheless sn2mp handles all three cases anyway, and will STRIP OFF +# trailing slashes if they occur 0, 1, or 2 times. +# +# Lastly, the long url that fpaste shows the user contains one trailing slash +# (due to the /[secret] part from how stickynotes worked). So we add one final +# rewrite that redirects users who go to that, to the non-slash version. +# +# If you have made it this far, you are a champion. You should get a badge. +# +# Warm regards and good luck, +# relrod + RewriteEngine on +#LogLevel alert rewrite:trace6 RewriteRule login / [L,R] RewriteCond %{HTTP_USER_AGENT} ^fpaste\/0\.3.*$ [OR] @@ -13,15 +118,10 @@ RewriteRule ^/$ /stickynotes2modernpaste/$1 [L,PT] RewriteCond %{HTTP_USER_AGENT} ^fpaste\/0\.3.*$ [OR] RewriteCond %{HTTP_USER_AGENT} ^Python\-urllib.*$ RewriteCond %{REQUEST_METHOD} POST -RewriteRule ^/(.*)=/$ /stickynotes2modernpaste/$1= [L,PT] - -RewriteCond %{HTTP_USER_AGENT} ^fpaste\/0\.3.*$ [OR] -RewriteCond %{HTTP_USER_AGENT} ^Python\-urllib.*$ -RewriteCond %{REQUEST_METHOD} POST -RewriteRule ^/(.*)=//$ /stickynotes2modernpaste/$1= [L,PT] +RewriteRule ^/paste/(.{22,24})/$ /stickynotes2modernpaste/paste/$1 [L,PT] # Otherwise, if we're given a URL with a trailing slash, kill it. -RewriteRule ^/(.*)=/$ /$1= [R,L] +RewriteRule ^/paste/([^/]{22,24})/$ /paste/$1 [R,L] WSGIScriptAlias / /usr/share/modern-paste/modern_paste.wsgi