async def get(self, removal: str) -> None: # type: ignore """Look up if the user visiting this page has the removal id for a certain paste. If they do they're authorized to remove the paste.""" if defensive.ratelimit(self.request, area="delete"): raise error.RatelimitError() with database.session() as session: paste = (session.query(database.Paste).filter( database.Paste.removal == removal).first()) if not paste: log.info("RemovePaste.get: someone visited with invalid id") raise tornado.web.HTTPError(404) if paste.exp_date < datetime.utcnow(): session.delete(paste) session.commit() log.warn( "Remove.get: paste was expired, is your cronjob running?") raise tornado.web.HTTPError(404) session.delete(paste) session.commit() self.redirect("/")
async def post(self) -> None: if defensive.ratelimit(self.request, area="delete"): raise error.RatelimitError() with database.session() as session: paste = (session.query(database.Paste).filter( database.Paste.removal == self.get_body_argument( "removal_id")).first()) if not paste: self.set_status(400) return session.delete(paste) session.commit() # this is set this way because tornado tries to protect us # by not allowing lists to be returned, looking at this code # it really shouldn't be a list but we have to keep it for # backwards compatibility self.set_header("Content-Type", "application/json") self.write( json.dumps([{ "paste_id": paste.slug, "status": "removed" }]))
async def get(self, slug: str) -> None: # type: ignore if defensive.ratelimit(self.request, area="read"): raise error.RatelimitError() with database.session() as session: paste = (session.query( database.Paste).filter(database.Paste.slug == slug).first()) if not paste: raise tornado.web.HTTPError(404) if paste.exp_date < datetime.utcnow(): session.delete(paste) session.commit() log.warn( "Show.get: paste was expired, is your cronjob running?") raise tornado.web.HTTPError(404) self.write({ "paste_id": paste.slug, "raw": paste.files[0].raw, "fmt": paste.files[0].fmt, "lexer": paste.files[0].lexer, "expiry": paste.exp_date.isoformat(), "filename": paste.files[0].filename, })
async def get(self, file_id: str) -> None: # type: ignore """Get a file from the database and download it in the plain.""" if defensive.ratelimit(self.request, area="read"): raise error.RatelimitError() with database.session() as session: file = (session.query( database.File).filter(database.File.slug == file_id).first()) if not file: raise tornado.web.HTTPError(404) if file.paste.exp_date < datetime.utcnow(): session.delete(file.paste) session.commit() log.warn( "FileDownload.get: paste was expired, is your cronjob running?" ) raise tornado.web.HTTPError(404) self.set_header("Content-Type", "text/plain; charset=utf-8") if file.filename: filename = ( f"{utility.filename_clean(file.filename)}-{file.slug}.txt") else: filename = f"{file.slug}.txt" self.set_header("Content-Disposition", f"attachment; filename={filename}") self.write(file.raw)
async def get(self, slug: str) -> None: # type: ignore """Fetch paste from database by slug and render the paste.""" if defensive.ratelimit(self.request, area="read"): raise error.RatelimitError() with database.session() as session: paste = (session.query( database.Paste).filter(database.Paste.slug == slug).first()) if not paste: raise tornado.web.HTTPError(404) if paste.exp_date < datetime.utcnow(): session.delete(paste) session.commit() log.warn( "Show.get: paste was expired, is your cronjob running?") raise tornado.web.HTTPError(404) can_delete = self.get_cookie("removal") == str(paste.removal) self.render( "show.html", paste=paste, pagetitle=f"View paste {paste.slug}", can_delete=can_delete, linenos=False, )
async def get(self, slug: str) -> None: # type: ignore """Render the new paste form, optionally have a lexer preselected from the URL.""" if defensive.ratelimit(self.request, area="read"): raise error.RatelimitError() with database.session() as session: paste = (session.query( database.Paste).filter(database.Paste.slug == slug).first()) if not paste: raise tornado.web.HTTPError(404) lexers_available = utility.list_languages() await self.render( "create.html", expiries=configuration.expiries, lexers=["text"], # XXX make this majority of file lexers? lexers_available=lexers_available, pagetitle="repaste", message=None, paste=paste, )
async def get(self, lexers: str = "") -> None: """Render the new paste form, optionally have a lexer preselected from the URL.""" if defensive.ratelimit(self.request, area="read"): raise error.RatelimitError() lexers_available = utility.list_languages() lexers_selected = [ lexer for lexer in lexers.split("+") if lexer.strip() ] # Our default lexer is just that, text if not lexers_selected: lexers_selected = ["text"] # Make sure all lexers are available if not all(lexer in lexers_available for lexer in lexers_selected): log.debug("CreatePaste.get: non-existent lexer requested") raise tornado.web.HTTPError(404) await self.render( "create.html", expiries=configuration.expiries, lexers=lexers_selected, lexers_available=lexers_available, pagetitle="Create new paste", message=None, paste=None, )
async def get(self) -> None: if defensive.ratelimit(self.request, area="read"): raise error.RatelimitError() self.write({ name: str(timedelta(seconds=delta)) for name, delta in configuration.expiries.items() })
def post(self) -> None: if defensive.ratelimit(self.request, area="create"): self.set_status(429) self.write("Enhance your calm, you have exceeded the ratelimit.") return lexer = self.get_body_argument("lexer", "text") raw = self.get_body_argument("raw", "", strip=False) expiry = self.get_body_argument("expiry", "1day") self.set_header("Content-Type", "text/plain") if lexer not in utility.list_languages(): log.info( "CurlCreate.post: a paste was submitted with an invalid lexer") self.set_status(400) self.write("Invalid `lexer` supplied.\n") return # Guard against empty strings if not raw or not raw.strip(): log.info("CurlCreate.post: a paste was submitted without raw") self.set_status(400) self.write("Invalid `raw` supplied.\n") return if expiry not in configuration.expiries: log.info("CurlCreate.post: a paste was submitted without raw") self.set_status(400) self.write("Invalid `expiry` supplied.\n") return paste = database.Paste(utility.slug_create(), configuration.expiries[expiry], "curl") file = database.File(paste.slug, raw, lexer) paste.files.append(file) with database.session() as session: session.add(paste) session.commit() # The removal cookie is set for the specific path of the paste it is # related to self.set_cookie("removal", str(paste.removal), path=f"/{paste.slug}") url_request = self.request.full_url() url_paste = urljoin(url_request, f"/{paste.slug}") url_removal = urljoin(url_request, f"/remove/{paste.removal}") url_raw = urljoin(url_request, f"/raw/{file.slug}") self.write( f"Paste URL: {url_paste}\nRaw URL: {url_raw}\nRemoval URL: {url_removal}\n" )
async def post(self) -> None: """This is a historical endpoint to create pastes, pastes are marked as old-web and will get a warning on top of them to remove any access to this route. pinnwand has since evolved with an API which should be used and a multi-file paste. See the 'CreateAction' for the new-style creation of pastes.""" lexer = self.get_body_argument("lexer") raw = self.get_body_argument("code", strip=False) expiry = self.get_body_argument("expiry") if defensive.ratelimit(self.request, area="create"): raise error.RatelimitError() if lexer not in utility.list_languages(): log.info("Paste.post: a paste was submitted with an invalid lexer") raise tornado.web.HTTPError(400) # Guard against empty strings if not raw or not raw.strip(): return self.redirect(f"/+{lexer}") if expiry not in configuration.expiries: log.info( "Paste.post: a paste was submitted with an invalid expiry") raise tornado.web.HTTPError(400) paste = database.Paste( utility.slug_create(), configuration.expiries[expiry], "deprecated-web", ) file = database.File(paste.slug, raw, lexer) paste.files.append(file) with database.session() as session: session.add(paste) session.commit() # The removal cookie is set for the specific path of the paste it is # related to self.set_cookie("removal", str(paste.removal), path=f"/{paste.slug}") # Send the client to the paste self.redirect(f"/{paste.slug}")
async def post(self) -> None: if defensive.ratelimit(self.request, area="create"): raise error.RatelimitError() lexer = self.get_body_argument("lexer") raw = self.get_body_argument("code", strip=False) expiry = self.get_body_argument("expiry") filename = self.get_body_argument("filename", None) if not raw or not raw.strip(): log.info("APINew.post: a paste was submitted without content") raise tornado.web.HTTPError(400) if lexer not in utility.list_languages(): log.info( "APINew.post: a paste was submitted with an invalid lexer") raise tornado.web.HTTPError(400) if expiry not in configuration.expiries: log.info( "APINew.post: a paste was submitted with an invalid expiry") raise tornado.web.HTTPError(400) paste = database.Paste( utility.slug_create(), configuration.expiries[expiry], "deprecated-api", ) paste.files.append(database.File(paste.slug, raw, lexer, filename)) with database.session() as session: session.add(paste) session.commit() req_url = self.request.full_url() location = paste.slug if filename: location += "#" + url_escape(filename) self.write({ "paste_id": paste.slug, "removal_id": paste.removal, "paste_url": urljoin(req_url, f"/{location}"), "raw_url": urljoin(req_url, f"/raw/{paste.files[0].slug}"), })
async def get(self) -> None: if defensive.ratelimit(self.request, area="read"): raise error.RatelimitError() try: with open(path.page / self.file) as f: html = docutils.core.publish_parts( f.read(), writer_name="html")["html_body"] except FileNotFoundError: raise tornado.web.HTTPError(404) self.render( "restructuredtextpage.html", html=html, pagetitle=(path.page / self.file).stem, )
async def get(self, paste_id: str) -> None: # type: ignore """Get all files from the database and download them as a zipfile.""" if defensive.ratelimit(self.request, area="read"): raise error.RatelimitError() with database.session() as session: paste = (session.query(database.Paste).filter( database.Paste.slug == paste_id).first()) if not paste: raise tornado.web.HTTPError(404) if paste.exp_date < datetime.utcnow(): session.delete(paste) session.commit() log.warn( "FileRaw.get: paste was expired, is your cronjob running?") raise tornado.web.HTTPError(404) data = io.BytesIO() with zipfile.ZipFile(data, "x") as zf: for file in paste.files: if file.filename: filename = f"{utility.filename_clean(file.filename)}-{file.slug}.txt" else: filename = f"{file.slug}.txt" zf.writestr(filename, file.raw) data.seek(0) self.set_header("Content-Type", "application/zip") self.set_header("Content-Disposition", f"attachment; filename={paste.slug}.zip") self.write(data.read())
async def get(self, file_id: str) -> None: # type: ignore """Get a file from the database and show it in hex.""" if defensive.ratelimit(self.request, area="read"): raise error.RatelimitError() with database.session() as session: file = (session.query( database.File).filter(database.File.slug == file_id).first()) if not file: raise tornado.web.HTTPError(404) if file.paste.exp_date < datetime.utcnow(): session.delete(file.paste) session.commit() log.warn( "FileRaw.get: paste was expired, is your cronjob running?") raise tornado.web.HTTPError(404) self.set_header("Content-Type", "text/plain; charset=utf-8") self.write(binascii.hexlify(file.raw.encode("utf8")))
async def post(self) -> None: if defensive.ratelimit(self.request, area="create"): raise error.RatelimitError() try: data = tornado.escape.json_decode(self.request.body) except json.decoder.JSONDecodeError: raise tornado.web.HTTPError(400, "could not parse json body") expiry = data.get("expiry") if expiry not in configuration.expiries: log.info( "Paste.post: a paste was submitted with an invalid expiry") raise tornado.web.HTTPError(400, "invalid expiry") auto_scale = data.get("long", None) is None files = data.get("files", []) if not files: raise tornado.web.HTTPError(400, "no files provided") with database.session() as session, utility.SlugContext( auto_scale) as slug_context: paste = database.Paste( next(slug_context), configuration.expiries[expiry], "v1-api", ) for file in files: lexer = file.get("lexer", "") content = file.get("content") filename = file.get("name") if lexer not in utility.list_languages(): raise tornado.web.HTTPError(400, "invalid lexer") if not content: raise tornado.web.HTTPError(400, "invalid content (empty)") try: paste.files.append( database.File( next(slug_context), content, lexer, filename, )) except error.ValidationError: raise tornado.web.HTTPError( 400, "invalid content (exceeds size limit)") if sum(len(f.fmt) for f in paste.files) > configuration.paste_size: raise tornado.web.HTTPError( 400, "invalid content (exceeds size limit)") paste.files[0].slug = paste.slug session.add(paste) try: session.commit() except Exception: # XXX be more precise log.warning("%r", slug_context._slugs) raise # Send the client to the paste url_request = self.request.full_url() url_paste = urljoin(url_request, f"/{paste.slug}") url_removal = urljoin(url_request, f"/remove/{paste.removal}") self.write({"link": url_paste, "removal": url_removal})
async def get(self) -> None: if defensive.ratelimit(self.request, area="read"): raise error.RatelimitError() self.write(utility.list_languages())
def post(self) -> None: # type: ignore """POST handler for the 'web' side of things.""" if defensive.ratelimit(self.request, area="create"): raise error.RatelimitError() expiry = self.get_body_argument("expiry") if expiry not in configuration.expiries: log.info( "CreateAction.post: a paste was submitted with an invalid expiry" ) raise error.ValidationError() auto_scale = self.get_body_argument("long", None) is None lexers = self.get_body_arguments("lexer") raws = self.get_body_arguments("raw", strip=False) filenames = self.get_body_arguments("filename") if not all([lexers, raws, filenames]): # Prevent empty argument lists from making it through raise error.ValidationError() if not all(raw.strip() for raw in raws): # Prevent empty raws from making it through raise error.ValidationError() if any(len(L) != len(lexers) for L in [lexers, raws, filenames]): log.info("CreateAction.post: mismatching argument lists") raise error.ValidationError() if any(lexer not in utility.list_languages() for lexer in lexers): log.info("CreateAction.post: a file had an invalid lexer") raise error.ValidationError() with database.session() as session, utility.SlugContext( auto_scale) as slug_context: paste = database.Paste(next(slug_context), configuration.expiries[expiry], "web") for (lexer, raw, filename) in zip(lexers, raws, filenames): paste.files.append( database.File( next(slug_context), raw, lexer, filename if filename else None, )) if sum(len(f.fmt) for f in paste.files) > configuration.paste_size: log.info("CreateAction.post: sum of files was too large") raise error.ValidationError() # For the first file we will always use the same slug as the paste, # since slugs are generated to be unique over both pastes and files # this can be done safely. paste.files[0].slug = paste.slug session.add(paste) session.commit() # The removal cookie is set for the specific path of the paste it is # related to self.set_cookie("removal", str(paste.removal), path=f"/{paste.slug}") # Send the client to the paste self.redirect(f"/{paste.slug}")