badsites.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. #!/usr/bin/python3
  2. # Author: Farooq Karimi Zadeh
  3. # Email: fkz@riseup.net
  4. # See LICENSE for licence information
  5. import pickle
  6. import cherrypy # <3
  7. import random
  8. import os
  9. from hashlib import sha512 # for password hashing
  10. import re
  11. import lmdb # <3
  12. from bsconfig import BSConfig
  13. SITE_RE = re.compile("([\w][^\/=\s]+)\/?|(^w{3}[\.\w][^\/\=\s]{2,})")
  14. # ^ I use this RE to determine if a valid url has been sent
  15. ENV = None # LMDB Environment
  16. @cherrypy.tools.register("before_handler")
  17. def auth(groups):
  18. global ENV
  19. sess = cherrypy.session
  20. if "login?" in sess:
  21. if sess["login?"] in groups:
  22. return
  23. else:
  24. raise cherrypy.HTTPRedirect("/permission")
  25. else:
  26. raise cherrypy.HTTPRedirect("/login")
  27. class Veil:
  28. def __init__(self, config):
  29. self.config = config
  30. self.templates = dict()
  31. self.env = ENV
  32. self.img_db = self.env.open_db(b"imgs")
  33. self.page_db = self.env.open_db(b"pages")
  34. self.admin_db = self.env.open_db(b"admins")
  35. self.img_exts = ("apng", "bmp", "gif",
  36. "ico", "cur", "jpg",
  37. "jpeg", "jfif", "pjpeg",
  38. "pjp", "png", "svg",
  39. "tif", "tiff", "webp")
  40. for template in os.listdir(config.templates_path):
  41. with open(config.templates_path + "/" + template) as fp:
  42. self.templates[template.replace(".html", "")] = fp.read()
  43. def is_img(self, addr):
  44. for ext in self.img_exts:
  45. if addr.endswith("." + ext):
  46. return True
  47. return False
  48. def __vote1(self, addr, ip):
  49. if self.is_img(addr):
  50. db = self.img_db
  51. else:
  52. db = self.page_db
  53. with self.env.begin(db=db, write=True) as txn:
  54. data = txn.get(addr)
  55. if data:
  56. data = pickle.loads(data)
  57. else:
  58. data = set()
  59. if type(data) != set:
  60. return True
  61. data.add(ip)
  62. txn.replace(addr.encode(), pickle.dumps(data))
  63. def __unvote1(self, addr, ip):
  64. if self.is_img(addr):
  65. db = self.img_db
  66. else:
  67. db = self.page_db
  68. with self.env.begin(write=True, db=db) as txn:
  69. data = txn.get(addr)
  70. if data:
  71. data = pickle.loads(data)
  72. if type(data) != set:
  73. return
  74. data.discard(ip)
  75. txn.replace(addr.encode(), data)
  76. @cherrypy.expose
  77. def permission(self):
  78. return "Sorry! Permission Denied!"
  79. @cherrypy.expose
  80. @cherrypy.tools.caching(delay=1)
  81. @cherrypy.tools.json_out()
  82. def index(self):
  83. result = dict()
  84. result["pages"] = dict()
  85. result["imgs"] = dict()
  86. with self.env.begin(db = self.page_db) as txn:
  87. for addr, ips in txn.cursor():
  88. result["pages"][addr] = len(ips) if type(ips) == set else -1
  89. with self.env.begin(db = self.img_db) as txn:
  90. for addr, ips in txn.cursor():
  91. result["imgs"][addr] = len(ips) if type(ips) == set else -1
  92. return result
  93. @cherrypy.expose
  94. def vote(self, addr):
  95. if addr.startswith("https"):
  96. addr = addr.replace("https", "http", 1)
  97. if addr.startswith("http"):
  98. self.__vote1(addr, cherrypy.request.remote.ip)
  99. return "Vote for the page successfully recorded"
  100. else:
  101. self.__vote1(addr, cherrypy.request.remote.ip)
  102. return "Vote for the page successfully recorded"
  103. @cherrypy.expose
  104. def unvote(self, addr):
  105. if addr.startswith("https"):
  106. addr = addr.replace("https", "http", 1)
  107. if addr.startswith("http"):
  108. self.__unvote1(addr, cherrypy.request.remote.ip)
  109. return "Vote for the page successfully removed"
  110. else:
  111. self.__unvote1(addr, cherrypy.request.remote.ip)
  112. return "Vote for the site successfully removed"
  113. @cherrypy.expose
  114. @cherrypy.tools.auth(groups=["tomato", "admin"])
  115. def addnew(self, data=None):
  116. if not data:
  117. return "Empty field: data"
  118. data = data.replace("\r", "")
  119. for line in data.split("\n"):
  120. line = line.strip()
  121. if not line:
  122. continue
  123. if line.startswith("https"):
  124. line = line.replace("https", "http", 1)
  125. if self.is_img(line):
  126. db = self.img_db
  127. else:
  128. db = self.page_db
  129. with self.env.begin(write=True, db=db) as txn:
  130. if txn.get(line.encode()):
  131. txn.replace(line.encode(), pickle.dumps(None))
  132. else:
  133. txn.put(line.encode(), pickle.dumps(None))
  134. return "Done :)"
  135. @cherrypy.expose
  136. @cherrypy.tools.auth(groups=["tomato", "admin"])
  137. def delete(self, addr):
  138. addr = addr.encode()
  139. if self.is_img(addr):
  140. db = self.img_db
  141. else:
  142. db = self.page_db
  143. with self.env.begin(write=True, db=db) as txn:
  144. if txn.delete(addr):
  145. return "Deleted!"
  146. else:
  147. return "No such thing in DB"
  148. @cherrypy.expose
  149. @cherrypy.tools.auth(groups=["tomato", "admin"])
  150. def panel(self):
  151. return self.templates["panel"]
  152. @cherrypy.expose
  153. def logout(self):
  154. if "login?" in cherrypy.session:
  155. cherrypy.session["login?"] = None
  156. raise cherrypy.HTTPRedirect("/login")
  157. else:
  158. return "You have not logged in..."
  159. @cherrypy.expose
  160. @cherrypy.tools.auth(groups=["tomato"])
  161. def addadmin(self, username="", password=""):
  162. if not (username and password):
  163. return self.templates["addadmin"]
  164. username = username.encode()
  165. password = password.encode()
  166. with self.env.begin(write=True, db=self.admin_db) as txn:
  167. if txn.get(username):
  168. txn.replace(username, password)
  169. else:
  170. txn.put(username, password)
  171. return "Well done :)"
  172. @cherrypy.expose
  173. def login(self, username="", password=""):
  174. if not(username and password):
  175. return self.templates["login"]
  176. if len(password) > 1024 or len(username) > 1024:
  177. return "Too long username or password"
  178. pass_hash = sha512(password.encode()).hexdigest()
  179. if username == "tomato" and pass_hash == self.config.tomato_pwd_hash:
  180. cherrypy.session["login?"] = "tomato"
  181. return "Welcome Tomato!"
  182. with self.env.begin(db=self.admin_db) as txn:
  183. the_hash = txn.get(username)
  184. if the_hash and the_hash.decode() == pass_hash:
  185. sess = cherrypy.session
  186. sess["login?"] = username
  187. raise cherrypy.HTTPRedirect("/panel")
  188. else:
  189. return "Incorrect username of password."
  190. if __name__ == "__main__":
  191. conf = {
  192. "/": {
  193. "tools.staticdir.root": os.path.abspath(os.getcwd()),
  194. "tools.sessions.on": True
  195. },
  196. }
  197. cherrypy.config.update({
  198. "server.socket_host": BSConfig.bindto,
  199. "server.socket_port": BSConfig.port})
  200. ENV = lmdb.open(BSConfig.db_path, max_dbs=3)
  201. cherrypy.quickstart(Veil(BSConfig), "/", conf)