Compare commits

...

13 Commits
v0.2 ... master

13 changed files with 62 additions and 35 deletions

View File

@ -28,7 +28,7 @@ HRSS is an application that allows you to transform any website into a RSS feed.
[Because we all love demos.](https://hrss.hipstercat.fr) [Because we all love demos.](https://hrss.hipstercat.fr)
# Installation # Installation (native)
1. Make sure you have Python3 installed and virtualenv. 1. Make sure you have Python3 installed and virtualenv.
2. Clone this repo for unstable branch, or download [latest release](https://hipstercat.fr/gogs/hipstercat/hrss/releases). 2. Clone this repo for unstable branch, or download [latest release](https://hipstercat.fr/gogs/hipstercat/hrss/releases).
@ -36,4 +36,7 @@ HRSS is an application that allows you to transform any website into a RSS feed.
4. `bin/activate && pip3 install -r requirements.txt` to install dependencies. 4. `bin/activate && pip3 install -r requirements.txt` to install dependencies.
5. `python3 manage.py migrate` to apply latest DB migrations (and create the H2 database file). 5. `python3 manage.py migrate` to apply latest DB migrations (and create the H2 database file).
6. `python3 manage.py runserver` to run the integrated webserver (not suitable for production use - even though you should not use HRSS in a production environnement at all until it is considered stable anyway). Use uWSGI to run it in a production environnement. 6. `python3 manage.py runserver` to run the integrated webserver (not suitable for production use - even though you should not use HRSS in a production environnement at all until it is considered stable anyway). Use uWSGI to run it in a production environnement.
# Installation (Docker)
Use https://github.com/kmlucy/docker-hrss nicely made by [kmlucy](https://github.com/kmlucy).

View File

@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/2.0/ref/settings/
""" """
import os import os
import urllib.parse
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -27,6 +28,9 @@ DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
# Set up your website base URL here, WITHOUT A TRAILING SLASH.
BASE_URL = "http://localhost:8000"
# Application definition # Application definition
@ -119,7 +123,7 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/ # https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/'+urllib.parse.urlparse(BASE_URL).path[1:]+'/static/' if urllib.parse.urlparse(BASE_URL).path[1:] else '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_ROOT = os.path.join(BASE_DIR, 'static')
USE_X_FORWARDED_HOST = True USE_X_FORWARDED_HOST = True
@ -156,4 +160,6 @@ LOGGING = {
'level': 'ERROR', 'level': 'ERROR',
} }
} }
} }
X_FRAME_OPTIONS = 'SAMEORIGIN'

View File

@ -16,8 +16,12 @@ Including another URLconf
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path
from django.conf.urls import include, url from django.conf.urls import include, url
from django.conf import settings
import urllib.parse
base_path = urllib.parse.urlparse(settings.BASE_URL).path[1:]
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path(base_path+'/admin/' if base_path else 'admin/', admin.site.urls),
url(r'', include('web.urls')), url(base_path+'/' if base_path else '', include('web.urls')),
] ]

View File

@ -1,3 +1,4 @@
from django.contrib import admin from django.contrib import admin
from .models import Feed
# Register your models here. admin.site.register(Feed)

View File

@ -1,11 +1,9 @@
from django.conf import settings
import urllib.parse
def BASE_URL(request): def BASE_URL(request):
""" """
Return a BASE_URL template context for the current request. Return a BASE_URL template context for the current request.
""" """
if request.is_secure(): parse = urllib.parse.urlparse(settings.BASE_URL)
scheme = 'https://' return {'BASE_URL': parse.scheme+"://"+parse.netloc, }
else:
scheme = 'http://'
fullhost = request.get_host()
base = fullhost.split(":")[0]
return {'BASE_URL': scheme + base, }

View File

@ -10,7 +10,7 @@ def random_url():
class Feed(models.Model): class Feed(models.Model):
url = models.URLField(max_length=255) url = models.URLField(max_length=255)
element = models.CharField(max_length=255) element = models.CharField(max_length=1000)
title = models.CharField(max_length=255) title = models.CharField(max_length=255)
content = models.CharField(max_length=255) content = models.CharField(max_length=255)
date = models.CharField(max_length=255) date = models.CharField(max_length=255)
@ -18,4 +18,4 @@ class Feed(models.Model):
link = models.CharField(max_length=255) link = models.CharField(max_length=255)
creation_date = models.DateTimeField(auto_now=True) creation_date = models.DateTimeField(auto_now=True)
uurl = models.CharField(max_length=18, default=random_url, unique=True) uurl = models.CharField(max_length=18, default=random_url, unique=True)

View File

@ -19,7 +19,7 @@
<div id="collapse{{ feed.id }}" class="collapse" aria-labelledby="heading{{ feed.id }}" data-parent="#accordionExample"> <div id="collapse{{ feed.id }}" class="collapse" aria-labelledby="heading{{ feed.id }}" data-parent="#accordionExample">
<div class="card-body"> <div class="card-body">
<code>{{ BASE_URL }}/{{ feed.uurl }}.rss</code> <code>{{ BASE_URL }}{% url 'rss' uurl=feed.uurl %}</code>
<p><a class="btn btn-danger" href="{% url 'feed_delete' feed.id %}">Delete</a></p> <p><a class="btn btn-danger" href="{% url 'feed_delete' feed.id %}">Delete</a></p>
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@
<div class="col-lg-12 text-center"> <div class="col-lg-12 text-center">
<h1 class="mt-5">Generate RSS out of any website</h1> <h1 class="mt-5">Generate RSS out of any website</h1>
{% if error %}<div class="alert alert-danger" role="alert">{{ error }}</div>{% endif %} {% if error %}<div class="alert alert-danger" role="alert">{{ error }}</div>{% endif %}
<form action="/" method="post"> <form action="{% url 'homepage' %}" method="post">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" name="url" id="url" placeholder="http://" {% if url %}value="{{ url }}"{% endif %}> <input type="text" class="form-control" name="url" id="url" placeholder="http://" {% if url %}value="{{ url }}"{% endif %}>

View File

@ -29,7 +29,7 @@
<code style="display:block" id="link-selector"></code> <code style="display:block" id="link-selector"></code>
</li> </li>
<li> <li>
<form action="/newfeed" method="post"> <form action="{% url 'newfeed' %}" method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" id="url" name="url" value="{{ url }}"> <input type="hidden" id="url" name="url" value="{{ url }}">
<input type="hidden" id="element" name="element" value=""> <input type="hidden" id="element" name="element" value="">
@ -44,7 +44,7 @@
</ul> </ul>
</div> </div>
<!-- /#sidebar-wrapper --> <!-- /#sidebar-wrapper -->
<iframe id="preview" style="width:100%" src="/iframe/{{ url }}"></iframe> <iframe id="preview" style="width:100%" src="{% url 'iframe' encodedurl=encodedurl %}"></iframe>
<script> <script>
$(function() { $(function() {
function handleResize() { function handleResize() {

View File

@ -26,14 +26,14 @@
<!-- Navigation --> <!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/">HRSS</a> <a class="navbar-brand" href="{% url 'homepage' %}">HRSS</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarResponsive"> <div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto"> <ul class="navbar-nav ml-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/">New <a class="nav-link" href="{% url 'homepage' %}">New
<span class="sr-only">(current)</span> <span class="sr-only">(current)</span>
</a> </a>
</li> </li>

View File

@ -3,8 +3,8 @@ from . import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.homepage, name='homepage'), url(r'^$', views.homepage, name='homepage'),
url(r'^iframe/(?P<url>.+)$', views.iframe, name='iframe'), url(r'^iframe/(?P<encodedurl>.+)$', views.iframe, name='iframe'),
url(r'^setup/(?P<url>.+)$', views.setup, name='setup'), url(r'^setup/(?P<encodedurl>.+)$', views.setup, name='setup'),
url(r'^newfeed$', views.newfeed, name='newfeed'), url(r'^newfeed$', views.newfeed, name='newfeed'),
url(r'^feeds$', views.feeds, name='feeds'), url(r'^feeds$', views.feeds, name='feeds'),
url(r'^feeds/delete/(?P<id>[0-9]+)$', views.feed_delete, name='feed_delete'), url(r'^feeds/delete/(?P<id>[0-9]+)$', views.feed_delete, name='feed_delete'),

View File

@ -67,6 +67,12 @@ def fetch_feed(feed, limit=10):
author = False author = False
try: try:
link = element.select(feed.link)[0]["href"] link = element.select(feed.link)[0]["href"]
if link and len(link) > 2 and link[0] == "/" and link[1] != "/":
# fixes issue #5 with relative link
# prepend base url
base_scheme = feed.url.split("://")[0]
base_url = feed.url.split("//")[-1].split("/")[0].split('?')[0]
link = base_scheme + "://" + base_url + link
except Exception: except Exception:
link = False link = False
items.append({"title": title, "content": content, "pubDate": date, "author": author, "link": link}) items.append({"title": title, "content": content, "pubDate": date, "author": author, "link": link})

View File

@ -5,12 +5,17 @@ from .models import Feed
from django.db.models import ObjectDoesNotExist from django.db.models import ObjectDoesNotExist
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import logging import logging
from urllib.parse import quote_plus, unquote_plus
import traceback
import sys
# Create your views here. # Create your views here.
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def iframe(request, url): def iframe(request, encodedurl):
sys.setrecursionlimit(10000)
try: try:
url = unquote_plus(encodedurl)
req = get_url(url) req = get_url(url)
html = req.content html = req.content
bs = False bs = False
@ -56,28 +61,32 @@ def iframe(request, url):
html = final_html html = final_html
except Exception as e: except Exception as e:
logger.debug(e) traceback.print_exc()
return HttpResponse("An error has occured", content_type=500) return HttpResponse("An error has occured", content_type=500)
return HttpResponse(html, content_type=content_type) return HttpResponse(html, content_type=content_type)
def dummy(request): def dummy(request):
return HttpResponse("toto") return HttpResponse("toto")
def homepage(request): def homepage(request):
if request.method == 'POST': if request.method == 'POST':
if "url" in request.POST and request.POST["url"]: if "url" in request.POST and request.POST["url"]:
url = request.POST["url"] url = request.POST["url"]
if is_valid_url(url): if is_valid_url(url):
return redirect("/setup/"+url) return redirect("setup", encodedurl=quote_plus(url))
else: else:
return render(request, 'homepage.html', {"url": url, "error": url+" is not a valid URL."}) return render(request, 'homepage.html', {"url": url, "error": url+" is not a valid URL."})
return render(request, 'homepage.html') return render(request, 'homepage.html')
def setup(request, url):
if is_valid_url(url): def setup(request, encodedurl):
return render(request, 'setup.html', {"url": url}) decoded_url = unquote_plus(encodedurl)
if is_valid_url(decoded_url):
return render(request, 'setup.html', {"encodedurl": encodedurl, "url": decoded_url})
else: else:
return redirect("/") return redirect("homepage")
def newfeed(request): def newfeed(request):
@ -110,9 +119,9 @@ def newfeed(request):
feed = Feed(url=url, element=element, title=title, content=content, date=date, author=author, link=link) feed = Feed(url=url, element=element, title=title, content=content, date=date, author=author, link=link)
feed.save() feed.save()
return redirect("/feeds") return redirect("feeds")
else: else:
return redirect("/") return redirect("homepage")
def feeds(request): def feeds(request):
feeds = Feed.objects.all() feeds = Feed.objects.all()
@ -124,11 +133,11 @@ def feed_delete(request, id):
if not request.get_host() == "hrss.hipstercat.fr:443": if not request.get_host() == "hrss.hipstercat.fr:443":
Feed.objects.get(pk=id).delete() Feed.objects.get(pk=id).delete()
logger.info("Removed feed ID "+id) logger.info("Removed feed ID "+id)
return redirect("/feeds") return redirect("feeds")
else: else:
return HttpResponse("Deleting is disabled on demo website.", status=403) return HttpResponse("Deleting is disabled on demo website.", status=403)
except ObjectDoesNotExist: except ObjectDoesNotExist:
return redirect("/feeds") return redirect("feeds")
def rss(request, uurl): def rss(request, uurl):
try: try: