<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Knowledge Base (Posts about webapps)</title><link>https://bgstack15.ddns.net/blog/</link><description></description><atom:link href="https://bgstack15.ddns.net/blog/categories/webapps.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 &lt;a href="mailto:bgstack15@gmail.com"&gt;bgstack15&lt;/a&gt; 
&lt;a rel="license" href="https://www.gnu.org/licenses/gpl-3.0.html"&gt;
&lt;img alt="GNU General Public License v3.0"
style="border-width:0; margin-bottom:12px;"
src="https://bgstack15.ddns.net/.images/gplv3-127x51.png"&gt;&lt;/a&gt;</copyright><lastBuildDate>Tue, 08 Sep 2026 20:59:23 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>LWSP upgrade for Luanti 5.17.0</title><link>https://bgstack15.ddns.net/blog/posts/2026/09/07/lwsp-upgrade-for-luanti-5-17-0/</link><dc:creator>bgstack15</dc:creator><description>&lt;p&gt;Luanti's recent release of version &lt;a href="https://bgstack15.ddns.net/blog/outbound/https:/github.com/luanti-org/luanti/releases/tag/5.17.0"&gt;5.17.0&lt;/a&gt; added a feature that validates ContentDB package author using a simple &lt;a href="https://bgstack15.ddns.net/blog/outbound/https:/github.com/luanti-org/luanti/commit/77e12fe21b94d5743109f95b8a47ac5fbde68294"&gt;regex&lt;/a&gt;, but &lt;a href="https://bgstack15.ddns.net/blog/posts/2025/09/20/novel-project-luanti-web-services-proxy/"&gt;Luanti WebServices Proxy&lt;/a&gt; overloads the author field with the backend server connection information, so I had to update LWSP to fix this.&lt;/p&gt;
&lt;p&gt;The validations fit entirely in this block, in file &lt;a href="https://bgstack15.ddns.net/blog/outbound/https:/github.com/luanti-org/luanti/blob/master/builtin/mainmenu/content/contentdb.lua"&gt;builtin/mainmenu/content/contentdb.lua&lt;/a&gt;&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;#packages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;release&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"number"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;"^[%w _%-.]+$"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;"^[a-z0-9_]+$"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;"warning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"ContentDB: Dropping invalid package: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nc"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So it's lua, thankfully, which is way easier for me to read than C++. An easy fix would be to adjust the client lua file to add a colon and tilde in the package author regex.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;package.author:match("^[%w _%-.~:]+$") and
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But seeing as how nobody else will adjust their client configs to allow that, so I &lt;a href="https://bgstack15.ddns.net/blog/cgit/luanti/lwsp/commit/?id=d79a5bb9235f8c05bfaaf453ecf89af5d64be588"&gt;adjusted lwsp&lt;/a&gt; to split author and host+proto with &lt;code&gt;_AT_&lt;/code&gt; rather than &lt;code&gt;~&lt;/code&gt;, and also replace a colon with &lt;code&gt;_colon_&lt;/code&gt;. It's longer and a little less pretty, but it matches the vanilla client expectations. Not everyone who might use lwsp will use a stackrpms build of Luanti.&lt;/p&gt;</description><category>luanti</category><category>regex</category><category>webapps</category><guid>https://bgstack15.ddns.net/blog/posts/2026/09/07/lwsp-upgrade-for-luanti-5-17-0/</guid><pubDate>Mon, 07 Sep 2026 12:40:00 GMT</pubDate></item><item><title>Progressive web app with share target for Android</title><link>https://bgstack15.ddns.net/blog/posts/2025/07/06/progressive-web-app-with-share-target-for-android/</link><dc:creator>bgstack15</dc:creator><description>&lt;div&gt;&lt;p&gt;I have known for a while that "progressive web apps" (apparently only supported in one browser) can be "installed" as an "app" on a mobile device, and they can be the target for receiving shared items! I finally bothered to learn how to do this with my flask app &lt;a href="https://bgstack15.ddns.net/blog/cgit/stackbin/log/?h=add-pwa"&gt;stackbin&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;There's a large number of steps required, and I ripped pretty much all of this (minus flask-specific troubleshooting) from reference 1. The main parts include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add file &lt;code&gt;manifest.json&lt;/code&gt; on the top level virtual path of your application. I had to add some jinja2 templating to handle the static prefix for my app.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href="https://bgstack15.ddns.net/blog/posts/2025/07/06/progressive-web-app-with-share-target-for-android/"&gt;Read more…&lt;/a&gt; (4 min remaining to read)&lt;/p&gt;&lt;/div&gt;</description><category>android</category><category>clipboard</category><category>flask</category><category>webapps</category><guid>https://bgstack15.ddns.net/blog/posts/2025/07/06/progressive-web-app-with-share-target-for-android/</guid><pubDate>Sun, 06 Jul 2025 12:43:00 GMT</pubDate></item><item><title>File Upload and Storage Service</title><link>https://bgstack15.ddns.net/blog/posts/2021/01/21/file-upload-and-storage-service/</link><dc:creator>bgstack15</dc:creator><description>&lt;p&gt;I present to the Internet my take on a python+flask web app! File Upload and
Storage Service is a small webapp for uploading and serving files. Please run
an instance behind authentication of some sort (which could be at the web
server level) so malicious actors do not upload bad things to your server.
Check out the code at &lt;a href="https://gitlab.com/bgstack15/fuss"&gt;https://gitlab.com/bgstack15/fuss&lt;/a&gt;. FUSS includes some
cool features, like drag-and-drop upload on the optional upload page. If you
disable the separate upload page, then users can just curl to the application
base URL. The application dynamically discovers the path that the user used to
get to the app! But admins who prefer to actually enforce a specific URL for
the application can configure that in the config file. A sysvinit service
script is provided, as well as a systemd service file. A dpkg recipe and also
rpm spec are included. To run this application on CentOS 7, you will need pip3
and a few modules from it. &lt;img alt="Main page of FUSS web
app" src="https://bgstack15.ddns.net/blog/2021/01/fuss-main-page.png"&gt; This
program is designed mostly as a demo of all the various bits: javascript,
python and flask, apache reverse proxying, and html.&lt;/p&gt;</description><category>flask</category><category>gpl</category><category>python</category><category>webapps</category><guid>https://bgstack15.ddns.net/blog/posts/2021/01/21/file-upload-and-storage-service/</guid><pubDate>Thu, 21 Jan 2021 13:35:59 GMT</pubDate></item><item><title>Domains for cookies for office.com</title><link>https://bgstack15.ddns.net/blog/posts/2016/10/27/domains-for-cookies-for-office-com/</link><dc:creator>bgstack15</dc:creator><description>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;As part of my security measures, I don't enable cookies by default for all
sites. I enable them per-site, on an as-needed basis. So my whitelist is very
large, but I think it helps more than it hinders.&lt;/p&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;So modern web applications depends on lots and lots of cookies. The office.com
applications depend on a large set of domains. Here is what I have learned so
far: &lt;code&gt;adfs.example.com (my company's ADFS page) excel.officeapps.live.com
live.com login.live.com login.microsoftonline.com login.windows.net office.com
office.live.com office365.com portal.office.com powerpoint.officeapps.live.com
products.office.com word-edit.officeapps.live.com&lt;/code&gt;&lt;/p&gt;</description><category>cookies</category><category>office</category><category>webapps</category><guid>https://bgstack15.ddns.net/blog/posts/2016/10/27/domains-for-cookies-for-office-com/</guid><pubDate>Thu, 27 Oct 2016 11:54:21 GMT</pubDate></item></channel></rss>