]> git.ipfire.org Git - ipfire.org.git/blobdiff - src/backend/nopaste.py
backend: show checksum on thank-you page
[ipfire.org.git] / src / backend / nopaste.py
index cb621cd9615eeb8937739b95e8135bafbec343b9..43f5552a962611b34faf56ee1be90ac99f48ce28 100644 (file)
 #!/usr/bin/python3
 
+import asyncio
 import datetime
+import io
+import ipaddress
+import logging
+import magic
+import struct
+import tornado.iostream
+import tornado.tcpserver
 
+from . import base
 from .misc import Object
+from .decorators import *
+
+# Setup logging
+log = logging.getLogger(__name__)
+
+CHUNK_SIZE = 1024 ** 2
 
 class Nopaste(Object):
-       def create(self, subject, content, mimetype="text", expires=None, account=None, address=None):
-               self._cleanup_database()
+       def _get_paste(self, query, *args, **kwargs):
+               return self.db.fetch_one(Paste, query, *args, **kwargs)
+
+       def create(self, content, account, subject=None, mimetype=None, expires=None, address=None):
+               # Convert any text to bytes
+               if isinstance(content, str):
+                       content = content.encode("utf-8")
 
-               uid = None
-               if account:
-                       uid = account.uid
+               # Store the blob
+               blob_id = self._store_blob(content)
+
+               # Guess the mimetype if none set
+               if not mimetype:
+                       mimetype = magic.from_buffer(content, mime=True)
 
                if expires:
                        expires = datetime.datetime.utcnow() + datetime.timedelta(seconds=expires)
 
                # http://blog.00null.net/easily-generating-random-strings-in-postgresql/
-               res = self.db.get("INSERT INTO nopaste(uuid, subject, content, time_expires, address, \
-                       uid, mimetype, size) VALUES(random_slug(), %s, %s, %s, %s, %s, %s, %s) RETURNING uuid",
-                       subject, content, expires, address, uid, mimetype, len(content))
+               paste = self._get_paste("""
+                       INSERT INTO
+                               nopaste
+                       (
+                               uuid,
+                               account,
+                               subject,
+                               expires_at,
+                               address,
+                               mimetype,
+                               size,
+                               blob_id
+                       )
+                       VALUES
+                       (
+                               random_slug(), %s, %s, %s, %s, %s, %s, %s
+                       )
+                       RETURNING
+                               *
+                       """, account.uid, subject, expires or None, address, mimetype, len(content), blob_id,
+               )
+
+               # Log result
+               log.info("Created a new paste (%s) of %s byte(s) from %s (%s - %s)" % (
+                       paste.uuid, paste.size, paste.address, paste.asn or "N/A", paste.country or "N/A",
+               ))
+
+               return paste
 
-               if res:
-                       return res.uuid
+       def _fetch_blob(self, id):
+               blob = self.db.get("""
+                       SELECT
+                               data
+                       FROM
+                               nopaste_blobs
+                       WHERE
+                               id = %s
+                       """, id,
+               )
+
+               if blob:
+                       return blob.data
+
+       def _store_blob(self, data):
+               """
+                       Stores the blob by sending it to the database and returning its ID
+               """
+               blob = self.db.get("""
+                       INSERT INTO
+                               nopaste_blobs
+                       (
+                               data
+                       )
+                       VALUES
+                       (
+                               %s
+                       )
+                       ON CONFLICT
+                       (
+                               digest(data, 'sha256')
+                       )
+                       DO UPDATE SET
+                               last_uploaded_at = CURRENT_TIMESTAMP
+                       RETURNING
+                               id
+                       """, data,
+               )
+
+               # Return the ID
+               return blob.id
 
        def get(self, uuid):
-               res = self.db.get("SELECT uuid, subject, time_created, time_expires, address, uid, \
-                       mimetype, views, size FROM nopaste WHERE uuid = %s AND (CASE WHEN time_expires \
-                       IS NULL THEN TRUE ELSE NOW() < time_expires END)", uuid)
-
-               if res:
-                       # Get the account that uploaded this if available
-                       res.account = None
-                       if res.uid:
-                               res.account = self.backend.accounts.get_by_uid(res.uid)
-
-                       # Touch the entry so it won't be deleted when it is still used
-                       self._update_lastseen(uuid)
-
-               return res
-
-       def get_content(self, uuid):
-               res = self.db.get("SELECT content FROM nopaste \
-                       WHERE uuid = %s", uuid)
-
-               if res:
-                       return bytes(res.content)
-
-       def _update_lastseen(self, uuid):
-               self.db.execute("UPDATE nopaste SET time_lastseen = NOW(), views = views + 1 \
-                       WHERE uuid = %s", uuid)
-
-       def _cleanup_database(self):
-               # Delete old pastes when they are expired or when they have not been
-               # accessed in a long time.
-               self.db.execute("DELETE FROM nopaste WHERE (CASE \
-                       WHEN time_expires IS NULL \
-                               THEN time_lastseen + INTERVAL '6 months' <= NOW() \
-                       ELSE NOW() >= time_expires END)")
+               paste = self._get_paste("""
+                       SELECT
+                               *
+                       FROM
+                               nopaste
+                       WHERE
+                               uuid = %s
+                       AND (
+                               expires_at >= CURRENT_TIMESTAMP
+                       OR
+                               expires_at IS NULL
+                       )
+                       """, uuid,
+               )
+
+               return paste
+
+       def cleanup(self):
+               """
+                       Removes all expired pastes and removes any unneeded blobs
+               """
+               # Remove all expired pastes
+               self.db.execute("""
+                       DELETE FROM
+                               nopaste
+                       WHERE
+                               expires_at < CURRENT_TIMESTAMP
+               """)
+
+               # Remove unneeded blobs
+               self.db.execute("""
+                       DELETE FROM
+                               nopaste_blobs
+                       WHERE NOT EXISTS
+                       (
+                               SELECT
+                                       1
+                               FROM
+                                       nopaste
+                               WHERE
+                                       nopaste.blob_id = nopaste_blobs.id
+                       )
+               """)
+
+
+class Paste(Object):
+       def init(self, id, data):
+               self.id, self.data = id, data
+
+       def __str__(self):
+               return self.subject or self.uuid
+
+       # 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
+
+       # Account
+
+       @lazy_property
+       def account(self):
+               return self.backend.accounts.get_by_uid(self.data.account)
+
+       # 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
+
+       # MIME Type
+
+       @property
+       def mimetype(self):
+               return self.data.mimetype or "application/octet-stream"
+
+       # 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_as(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)
+
+       # Viewed?
+
+       def viewed(self):
+               """
+                       Call this when this paste has been viewed/downloaded/etc.
+               """
+               self.db.execute("""
+                       UPDATE
+                               nopaste
+                       SET
+                               last_accessed_at = CURRENT_TIMESTAMP,
+                               views = views + 1
+                       WHERE
+                               id = %s
+                       """, self.id,
+               )