]> git.ipfire.org Git - ipfire.org.git/commitdiff
nopaste: Create a service that takes pastes through a TCP connection
authorMichael Tremer <michael.tremer@ipfire.org>
Thu, 22 Feb 2024 18:42:32 +0000 (18:42 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 22 Feb 2024 18:42:32 +0000 (18:42 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/backend/nopaste.py
src/scripts/ipfire.org-webapp.in

index d6101a26627553214c711104993b1015fb68c4d9..42b5fab92147ff09fd6e29a990a3c08528c4afa7 100644 (file)
@@ -1,10 +1,21 @@
 #!/usr/bin/python3
 
+import asyncio
 import datetime
+import io
+import logging
 import magic
+import tornado.iostream
+import tornado.tcpserver
 
+from . import base
 from .misc import Object
 
+# Setup logging
+log = logging.getLogger(__name__)
+
+CHUNK_SIZE = 1024 ** 2
+
 class Nopaste(Object):
        def create(self, content, subject=None, mimetype=None, expires=None, account=None, address=None):
                self._cleanup_database()
@@ -112,3 +123,127 @@ class Nopaste(Object):
                        WHEN time_expires IS NULL \
                                THEN time_lastseen + INTERVAL '6 months' <= NOW() \
                        ELSE NOW() >= time_expires END)")
+
+
+class Paste(Object):
+       def init(self, id, data):
+               self.id, self.data = id, data
+
+       # UUID
+
+       @property
+       def uuid(self):
+               return self.data.uuid
+
+       # Subject
+
+       @property
+       def subject(self):
+               return self.data.subject
+
+       # Created At
+
+       @property
+       def created_at(self):
+               return self.data.created_at
+
+       time_created = created_at
+
+       # Expires At
+
+       @property
+       def expires_at(self):
+               return self.data.expires_at
+
+       time_expires = expires_at
+
+       # Blob
+
+       @lazy_property
+       def blob(self):
+               return self.backend.nopaste._fetch_blob(self.data.blob_id)
+
+       content = blob
+
+       # Size
+
+       @property
+       def size(self):
+               return self.data.size
+
+       # Address
+
+       @property
+       def address(self):
+               return self.data.address
+
+       # Location
+
+       @lazy_property
+       def location(self):
+                return self.backend.location.lookup("%s" % self.address)
+
+       # ASN
+
+       @lazy_property
+       def asn(self):
+               if self.location and self.location.asn:
+                       return self.backend.location.get_asn(self.location.asn)
+
+       # Country
+
+       @lazy_property
+       def country(self):
+               if self.location and self.location.country_code:
+                       return self.backend.location.get_country(self.location.country_code)
+
+
+class Service(tornado.tcpserver.TCPServer):
+       def __init__(self, config, **kwargs):
+               # Initialize backend
+               self.backend = base.Backend(config)
+
+               super().__init__(**kwargs)
+
+       async def handle_stream(self, stream, address):
+               buffer = io.BytesIO()
+
+               # Read the entire stream
+               try:
+                       while True:
+                               chunk = await stream.read_bytes(CHUNK_SIZE, partial=True)
+
+                               log.debug("Read a chunk of %s byte(s)" % len(chunk))
+
+                               # Write the chunk into the buffer
+                               buffer.write(chunk)
+
+                               # If we have read less then we have reached the end
+                               if len(chunk) < CHUNK_SIZE:
+                                       break
+
+               # End if the stream closed unexpectedly
+               except tornado.iostream.StreamClosedError as e:
+                       return
+
+               log.debug("Finished reading payload")
+
+               # Process address
+               address, port = address
+
+               # Store this into the database
+               with self.backend.db.transaction():
+                       uuid = self.backend.nopaste.create(
+                               buffer.getvalue(),
+                               subject="Streamed Upload",
+                               address=address,
+                       )
+
+               # Format a response message
+               message = "https://nopaste.ipfire.org/view/%s\n" % uuid
+
+               # Send the message
+               await stream.write(message.encode("utf-8"))
+
+               # We are done, close the stream
+               stream.close()
index 0c8a0e7ff50fe69ce4af837074b30ed4aec61466..86a7b1d3382e669aa5a9c7218b4fd0ac79d58c55 100755 (executable)
@@ -6,6 +6,7 @@ import tornado.options
 tornado.options.define("debug", type=bool, default=False, help="Enable debug mode")
 tornado.options.define("port", type=int, default=8001, help="Port to listen on")
 
+import ipfire.nopaste
 from ipfire.web import Application
 
 async def run():
@@ -16,6 +17,10 @@ async def run():
                debug=tornado.options.options.debug)
        app.listen(tornado.options.options.port, xheaders=True)
 
+       # Initialize the nopaste service
+       nopaste = ipfire.nopaste.Service("@configsdir@/@PACKAGE_NAME@.conf")
+       nopaste.listen(tornado.options.options.port + 1000)
+
        # Wait for forever
        await asyncio.Event().wait()