Gemlog Booster [main]

Configurable file locations in addition to Gemlog

=> 1e13c94ae4967b6676007027a922d7e88874a20c

diff --git a/README.md b/README.md
index 6c04597..fbccb70 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ This is a simple Python script that listens for Titan requests and then adds, up
 The configuration file _~/.booster/config.json_ must exist:
 
 ```json
-{    
+{
     "python": "/usr/bin/python3",
     "git": {
         "exec": "/usr/bin/git",
@@ -18,8 +18,15 @@ The configuration file _~/.booster/config.json_ must exist:
     "cert": "/absolute-path/titan.crt",
     "key": "/absolute-path/titan.key",
     "authorized": "/absolute-path/titan-clients.pem",
-    "file_root": "/absolute-path-where-files-are-kept/",
     "site_url": "gemini://skyjake.fi",
-    "root": "/gemlog"
+    "files": {
+        "root": "/absolute-path/capsule-files/",
+        "Gemlog": {
+            "subdir": "gemlog/"
+        },
+        "Title prefix for Git": {
+            "file": "relative-path-of-single-file.gmi"
+        }
+    }
 }
 ```
diff --git a/booster.py b/booster.py
index d4e9d09..a2636da 100755
--- a/booster.py
+++ b/booster.py
@@ -30,11 +30,15 @@
 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 # POSSIBILITY OF SUCH DAMAGE.
 
-import os, sys, json, subprocess
+import os, sys, json, subprocess, time
 import socket, ssl, OpenSSL.crypto, hashlib
 from urllib.parse import urlparse
 pjoin = os.path.join
 
+def append_slash(p):
+    if not p.endswith('/'): return p + '/'
+    return p
+
 #----------------------------------------------------------------------------#
 #                               CONFIGURATION                                #
 #----------------------------------------------------------------------------#
@@ -45,9 +49,8 @@ if not os.path.exists(CFG_PATH):
     print(f'ERROR: Configuration file {CFG_PATH} not found.')
     sys.exit(1)
 CONFIG = json.loads(open(CFG_PATH, 'rt').read())
-if not CONFIG['root'].endswith('/'):
-    CONFIG['root'] = CONFIG['root'] + '/'
 print(json.dumps(CONFIG, indent=2))
+FILES_ROOT = append_slash(CONFIG['files']['root'])
 
 #----------------------------------------------------------------------------#
 #                             REQUEST HANDLING                               #
@@ -75,6 +78,7 @@ def urlenc(q):
     return q
 
 def handle_client(stream):
+    print(time.strftime('%Y-%m-%d %H:%M:%S'))
     data = bytes()
     incoming = stream.recv(1024)
     expected_size = -1
@@ -105,54 +109,81 @@ def handle_client(stream):
         if len(data) >= expected_size:
             break
         incoming = stream.recv(1024)
-    # process the request
+
     print(f'URL   : {req_url}')
     print(f'Token : {req_token}')
     print(f'MIME  : {req_mime}')
     print(f'Data  : {len(data)} bytes')
+
     parts = urlparse(req_url)
     path = parts.path
-    if not path.startswith(CONFIG['root']):
-        report_error(stream, 51, "invalid path")
-        return
-    path = path[len(CONFIG['root']):]
-    if path.startswith('.'):
+    file_path = None
+    msg_path = None
+    if (path.startswith('.') or
+        os.path.basename(path).startswith('.') or
+        '..' in path):
         report_error(stream, 61, "access is not authorized")
         return
-    file_path = pjoin(CONFIG['file_root'], path)
-    view_url = '%s/%s%s' % (CONFIG['site_url'], CONFIG['root'], path)
+
+    # Are we allowed to edit this path?
+    msg_prefix = ''
+    for file_group in CONFIG['files']:
+        group = CONFIG['files'][file_group]
+        if type(group) != dict: continue
+        if 'subdir' in group:
+            auth_prefix = append_slash(group['subdir'])
+            msg_path    = path[len(auth_prefix):]
+        elif 'file' in group:
+            auth_prefix = '/' + group['file']
+            msg_path    = os.path.basename(path)
+        if path.startswith(auth_prefix):
+            file_path = os.path.normpath(FILES_ROOT + path)
+            if not file_path.startswith(FILES_ROOT) or \
+               os.path.isdir(file_path):
+                report_error(stream, 61, "access is not authorized")
+                return
+            msg_prefix  = f'{file_group}: '
+            break
+    if not file_path:
+        report_error(stream, 61, "unauthorized location")
+        return
+
+    # Process the request.
+    is_new   = not os.path.exists(file_path)
+    view_url = '%s%s' % (CONFIG['site_url'], path)
     response = '# Booster log\n'
-    is_new = not os.path.exists(file_path)
-    GIT = CONFIG['git']
     if not is_new and req_token == 'DELETE':
         response += f'* deleting file: {file_path}\n'
         os.remove(file_path)
-        git_msg = GIT['message_prefix'] + 'Removed ' + path
+        git_msg = msg_prefix + 'Removed ' + msg_path
     else:
         if is_new:
             response += f'* creating a new file: {file_path}\n'
-            git_msg = GIT['message_prefix'] + 'Added ' + path
+            git_msg = msg_prefix + 'Added ' + msg_path
         else:
             response += f'* updating file: {file_path}\n'
-            git_msg = GIT['message_prefix'] + 'Updated ' + path
+            git_msg = msg_prefix + 'Updated ' + msg_path
         # write the data
         response += f'* writing %d bytes\n' % len(data)
         dst = open(file_path, 'wb')
         dst.write(data)
         dst.close()
-    # update indices
-    os.chdir(CONFIG['file_root'])
+
+    # Update Gemlog indices.
+    GEMLOG_SUBDIR = CONFIG['files']['Gemlog']['subdir']
+    os.chdir(pjoin(FILES_ROOT, GEMLOG_SUBDIR))
     response += f'* updating indices\n'
     response += run_command([CONFIG['python'], ".makeindex.py"])
     response += '* committing changes to Git repository\n'
     if is_new:
-        response += run_command([GIT['exec'], 'add', file_path])
-    response += run_command([GIT['exec'], 'commit', '-a', '-m', git_msg])
+        response += run_command([CONFIG['git'], 'add', file_path])
+    response += run_command([CONFIG['git'], 'commit', '-a', '-m', git_msg])
     response += "\n"
     response += f"=> {view_url} View the page\n"
     response += f"=> gemini://warmedal.se/~antenna/submit?%s Notify Antenna" %\
-        urlenc('%s%s' % (CONFIG['site_url'], CONFIG['root']))
-    # compose a response
+        urlenc('%s%s' % (CONFIG['site_url'], GEMLOG_SUBDIR))
+
+    # Send the response.
     stream.sendall(('20 text/gemini; charset=utf-8\r\n%s' %
                      response).encode('utf-8'))
 
Proxy Information
Original URL
gemini://git.skyjake.fi/booster/main/cdiff/1e13c94ae4967b6676007027a922d7e88874a20c
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
29.359341 milliseconds
Gemini-to-HTML Time
0.341111 milliseconds

This content has been proxied by September (ba2dc).