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] 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 ^/paste/(.{22,24})/$ /stickynotes2modernpaste/paste/$1 [L,PT] # Otherwise, if we're given a URL with a trailing slash, kill it. RewriteRule ^/paste/([^/]{22,24})/$ /paste/$1 [R,L] WSGIScriptAlias / /usr/share/modern-paste/modern_paste.wsgi WSGIProcessGroup stickynotes2modernpaste WSGIApplicationGroup %{GLOBAL} WSGIScriptReloading On Order deny,allow Require all granted Require all granted DocumentRoot /usr/share/modern-paste #ErrorLog logs/modern-paste-error.log #CustomLog logs/modern-paste-access.log combined #LogLevel info