Python CGI Skript - Aufruf läuft nicht richtig.

Django, Flask, Bottle, WSGI, CGI…
Antworten
valentinks
User
Beiträge: 20
Registriert: Sonntag 27. Juni 2021, 10:31
Wohnort: Kassel

Hallo zusammen,
seit einiger Zeit versuche ich mich jetzt mit Python. Leider hilft das Buch nicht wirklich da ich immer wieder auf Schwierigkeiten stoße. In der Hoffnung die Probleme mit dem localhost zu umgehen habe ich jetzt, auf einem anderen Rechner, einen Server laufen (Ubuntu Server 20.04 mit Apache2). Aber auch hier laufen die Skripte aus dem Buch, bzw Download nicht. (Dem Server habe ich gleich nach Installation ein Update verpasst, die Browser auf meinem Rechner sind alle aktuell.)

Durch ausgiebige Suche konnte ich schon die *.py-Dateien soweit anpassen, das die ursprünglichen Fehler nicht mehr auftreten. Diese waren:
  • redaktion.py: End of script output before headers: redaktion.py
  • abstimmung4.py:Response header name '<!--' contains invalid characters, aborting request, referer: http://homeserv.fritz.box/python/abstimmung/abstimmung.html
Dafür treten jetzt neue Fehler auf.
Abstimmung, Pfade:
  • /var/www/html/python/abstimmung/abstimmung4.html
  • /usr/lib/cgi-bin/abstimmung4.py
Nach Auswahl einer der drei Optionen und Bestätigung im html erfolgt die Weiterleitung zur py. Diese erstellt auch die gewünschte Seite, aber, es wird kein Cookie gesetzt, die Zähler zählen also beim aktualisieren der Seite hoch was eigentlich nicht sein sollte, im Browser wird über der eigentliche Seite eine Info ausgegeben die da nicht sein sollte.
Bei Firefox und Chromium: Set-Cookie: abstimmung=True
und bei Google Chrome Set-Cookie: abstimmung=True Set-Cookie: nc_sameSiteCookielax=true Set-Cookie: nc_sameSiteCookiestrict=true Set-Cookie: oc_sessionPassphrase=Rv6311eBYAjX9MJLTrkTY2x8jS3AQ0nAOxOWj4YemB3gqcdi2fJvzqh8XqW54fdae4nwwRF9Fg9liPRecvDWZGEQ6vw2%2B6NjcviHbc2HwXHtiQM6FVtdBRXqa%2FiVIVkD Set-Cookie: ocnoslie8ssv=a4aa54bac8019b45da7dc520731edb8a

Die abstimmung4.py:

Code: Alles auswählen

#!/usr/bin/python3

import cgi, cgitb, os, pickle, http.cookies
cgitb.enable()

print("Content-type: text/html\n")

SEITE = '''{}

<html>
    <head>
        <title>Online-Abstimmung</title>
    </head>
    <body>
        <h1>Online-Abstimmung</h1>
        <form action="http://homeserv.fritz.box/cgi-bin/abstimmung4.py">
            <h3>{}</h3>
            Hier ist das aktuelle Abstimmungsergebnis:<br>
            Frage: {}
            <br><br> {} </br>
        </form>
    </body>
</html>
'''

PFAD = 'zaehler4.txt'
ITEMS = ["Ja","Nein","Enthaltung"]
FRAGE = "Sind Studiengeb&uuml;hren an Unis sinnvoll?"

class Zaehler(object):
    def __init__(self, datei, items):
        self.datei = datei
        try:
            f = open(PFAD, 'rb')    # im Buch: datei
            self.stimmen = pickle.load(f)
            f.close()
        except:
            self.stimmen={}
            for i in items:
                self.stimmen[i]=0
            f=open(PFAD, 'wb')      # im Buch: datei
            pickle.dump(self.stimmen, f)
            f.close()

    def votiere(self,item):
        self.stimmen[item] += 1
        f=open(PFAD, 'wb')          # im Buch: self.datei
        pickle.dump(self.stimmen, f)
        f.close()

    def __str__(self):
        ergebnis = ""
        for i in self.stimmen.keys():
            ergebnis += '<b>{}: </b>{} Stimmen<br>\n'.format(i, self.stimmen[i])
        return ergebnis

class Abstimmung(object):
    def __init__(self):
        self.form = cgi.FieldStorage()
        self.zaehler = Zaehler(PFAD, ITEMS)
        if not self.__schon_mal_abgestimmt():
            if 'item' in self.form.keys():
                item=self.form.getvalue('item')
                self.zaehler.votiere(item)
            self.meldung = "Vielen Dank f&uuml;r Ihr Voting!"
        else:
            self.meldung = "Sorry, Sie haben bereits abgestimmt ..."

    def __schon_mal_abgestimmt(self):
        self.cookie = http.cookies.SimpleCookie()
        try:
            self.cookie.load(os.environ['HTTP_COOKIE'])
            return bool(self.cookie['abstimmung']) # im Buch fehlt am anfang ein Leerzeichen
        except:
            self.cookie['abstimmung'] =  True
            return False

    def __str__(self):
        return SEITE.format(self.cookie, self.meldung, FRAGE, self.zaehler)

print(Abstimmung())
Redaktion, Pfade:
  • /var/www/html/python/redaktion/login_redaktion.html
  • /usr/lib/cgi-bin/redaktion.py
Nach Eingabe von Benutzer und Passwort in der html wird die py aufgerufen. Die Seite bleibt jedoch leer, also weder die Erfolgs-Seite noch die Fehler-Seite wird angezeigt.

Die redaktion.py

Code: Alles auswählen

#!/usr/bin/python3

import sqlite3, cgi, hashlib, time, logging

logging.basicConfig(filename="tmp/logging.txt",
                    format="%(funcName)s: %(message)s",
                    level=logging.DEBUG,
                    filemode="w")                          #2

print("Content-type: text/html\n")

# Schablone für HTML-Seite
# Platzhalter {}: Name, Passwort, Text, Fehler im Beitrag,
# Fehler im Passwort


SEITE1 = """
<html>
<head>
  <title>Python-Redaktionssystem</title>
  <meta http-equiv="Content-Type" content="charset=utf-8" />
</head>
<body bgcolor=#C0C0C0>
  <h2>Python-Redaktionssystem</h2>
  <form method="POST" >
    <input type="hidden" name="name" value="{}">     
    <input type="hidden" name="passwort" value="{}">
    <b>Titel: </b>
    <input type="Text" name="titel"
           size="50" maxlength="80"><br><br>
    <textarea name="text" cols="50" rows="8" >{}
    </textarea><br>
    <h4>Haltbarkeit</h4>
    <input type="Radio" name="haltbar" value="14"
     checked="checked">
    2 Wochen <br>
    <input type="Radio" name="haltbar" value="30">
    1 Monat <br>
    <input type="Radio" name="haltbar" value="90">
    3 Monate <br>
    <input type="Radio" name="haltbar" value="180">
     6 Monate <br>
    <h4> Passwortverwaltung</h4>
    <input type="Checkbox" name="neuespass" value="1">
    Passwort &auml;ndern<br>
    <input type="Password" name="neupass1" > Neues Passwort<br>
    <input type="Password" name="neupass2" > Passwort wiederholen<br>
    <input type="Submit" value="Absenden">
  </form>
  <i>{}<br>{}<br></i>    
</body></html>"""

# HTML-Seite mit Fehlermeldung bei falschem Login
SEITE2 = """
<html>
<head><title>Rython-Redaktionssystem</title>
<meta http-equiv="Content-Type" content="charset=utf-8" />
</head>
<body bgcolor=#C0C0C0>
<h2> Python-Redaktionssystem</h2>
<form method="POST">
Name: <input type="Text" name="name" >&nbsp;
Passwort: <input type="Password"
name="passwort"><br><br>
<input type="Submit" value="Login">
</form>
<b> Login gescheitert! &Uuml;berpr&uuml;fen Sie Name und
Passwort.<b>
</body></html>"""

# Schablone für Webseite (Publikation)
# Platzhalter {}: Zeit und Beiträge
WEBSEITE = """<html>
<head><title>Python-News</title></head>
<body>
<h1>Python-News</h1>
Letzte &Auml;nderung: {}
{} 
</body></html>"""

# Schablone für Beitrag
# Platzhalter {}: Titel, Autor und Text
BEITRAG = """<h3>{}</h3>
<h4>von {}</h4>
<p>{}</p>"""

HTML = {ord("ä"): "&auml;", ord("ö"): "&ouml;",
        ord("ü"): "&uuml;", ord("Ä"): "&Auml;",
        ord("Ö"): "&Ouml;", ord("Ü"): "&Uuml;",
        ord("ß"): "&szlig;"}                         #2

class Person(object):                                #3
  def __init__(self, form, db):
    self.name = form.getvalue("name")
    self.pw = form.getvalue("passwort")
    self.db = db

  def id_ok(self):                                   #4
    logging.debug("Name: {}, Passwort:{}".format(
                                     self.name,self.pw))
    try:
        logging.debug(self.db)
        verbindung = sqlite3.connect(self.db)
        c = verbindung.cursor()
        c.execute("""SELECT *
                     FROM person
                     WHERE name = ?;""", (self.name,))
        logging.debug("Mit Datenbank verbunden.")
        fingerprint_db = list(c)[0][1]               #5
        pw_bytes = self.pw.encode("utf-8")           #6 
        fingerprint_pw = hashlib.md5(pw_bytes).digest()
        c.close()
        verbindung.close()
        return fingerprint_db == fingerprint_pw      #7

    except:
        return False

  def aktualisiere_pw(self, form):                   #8
    neupw1 = form.getvalue("neupass1", "")
    neupw2 = form.getvalue("neupass2", "")
    if neupw1 == neupw2: 
        self.pw = neupw1
        verbindung = sqlite3.connect(self.db)
        c = verbindung.cursor()
        c.execute("""UPDATE person
                     SET fingerprint = ?
                     WHERE name = ?;
                 """,
                (hashlib.md5(self.pw.encode("utf-8")).digest(),
                 self.name))
        verbindung.commit()
        c.close()
        verbindung.close()
        return "Passwort ge&auml;ndert."
    else:
        return "Fehler! Passw&ouml;rter nicht gleich!"
    
class Beitrag(object):                               #9
    def __init__(self, form, autor, db):
      self.text = self.titel = ""
      self.db = db
      self.autor = autor
      if "titel" in form.keys():
        self.text = form.getvalue("text")
        self.titel = form.getvalue("titel")
        sekunden = float(form.getvalue("haltbar")) *24*3600
        self.verfallsdatum = sekunden + time.time()
        logging.debug("Verfallsdatum:" + str(self.verfallsdatum))

    def publiziere(self):
        if self.titel:                               #10
          logging.debug("Titel: " + self.titel)
          logging.debug("Text: " + self.text)
          verbindung = sqlite3.connect(self.db)
          c = verbindung.cursor()
          c.execute("""SELECT *
                       FROM beitrag
                       WHERE titel = ?;""",
                     (self.titel,))
          if not list(c):                           #11
            c.execute("""INSERT INTO beitrag
                         VALUES(?, ?, ?, ?);""",
                      (self.titel, self.text,
                       self.verfallsdatum,
                       self.autor.name))
            logging.debug("Gespeichert: " + self.titel)
            verbindung.commit()
            self.text = ""
            self.titel = ""
            meldung = "Beitrag wurde gespeichert."
          else:
            meldung = "Titel existiert bereits."
          c.close()
          verbindung.close()
        else: meldung = ""
        return meldung

    def aktualisiere_news(self, news_pfad):
        # HTML-Datei mit Journal aktualisieren
        verbindung = sqlite3.connect(self.db)
        c = verbindung.cursor()
        beiträge = list(
          c.execute("""select * FROM beitrag;"""))  
        pubtext ="" 
        for (titel, text,  verfallsdatum, 
             autor) in beiträge:                   #12
          if float(verfallsdatum) > time.time():    
            pubtext += BEITRAG.format(
                         titel.translate(HTML), 
                         autor.translate(HTML),
                         text.translate(HTML))
          else:                                     #13
            c.execute("""DELETE FROM beitrag
                         WHERE titel = ?;""", (titel,))
        logging.debug("Veröffentlichter Text:"+pubtext)
        verbindung.commit()
        c.close()
        verbindung.close()
        # Speichern
        f = open(news_pfad, "w")
        f.write(WEBSEITE.format(time.asctime(), pubtext))
        f.close()

# Konstanten
DB = "redaktion.db"
NEWS_PFAD = "html/news.html"

# WSGI Applikationsfunktion
def application(environ, start_response):
  status = '200 OK' 
  form = cgi.FieldStorage(
            fp=environ['wsgi.input'],
            environ=environ,
            keep_blank_values=True)
  redakteur = Person(form, DB)                    #14
  beitrag = Beitrag(form, redakteur, DB)                         
  beitragfehler = ""                              #15
  pwfehler = ""
  if redakteur.id_ok():                           #16
      beitragfehler = beitrag.publiziere()
      beitrag.aktualisiere_news(NEWS_PFAD)
      if "neuespass" in form.keys():
          pwfehler = redakteur.aktualisiere_pw(form)
      content_string = SEITE1.format(redakteur.name,
                                     redakteur.pw,
                                     beitrag.text,
                                     beitragfehler,
                                     pwfehler)
      content = content_string.encode('utf-8')
    
  else:
    content = SEITE2.encode('utf-8')

  response_headers = [('Content-type', 'text/html'),
                      ('Content-Length', str(len(content)))]
  start_response(status, response_headers)
  return [content]

print(Beitrag)
In keinem der beiden Fälle zeigt die error.log noch einen Eintrag.

Kann mir jemand da weiterhelfen?
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn Du ein Buch hast, das Dir CGI beibringen will, dann taugt es nur dazu, dass Dein Tisch nicht wackelt. CGI benutzt man seit Anfang dieses Jahrtausends nicht mehr für ernsthafte Aufgaben.
Ungefähr genauso lang schreibt man nicht mehr &uuml; statt ü.
Cookies gehören in den HTTP-Header und nicht in den Body.
Für HTML benutzt man ein Template-System statt Stringformatierung. self.datei wird nicht benutzt, Dateien öffnet man mit dem with-Statement, und statt Pickle würde ich hier json benutzen. Nackte excepts benutzt man nicht, sondern fängt nur die Fehler ab, die man auch erwarten würde.
Wenn man von einem Wörterbuch sowohl Schlüssel als auch Werte braucht, benutzt man items().
__schon_mal_abgestimmt hat ein _ zu viel. `Abstimmung` ist keine Klasse, weil es nur aus einer Methode besteht und damit nur eine kompliziert geschriebene Funktion ist.

Eingerückt wird immer mit 4 Leerzeichen pro Ebene, nicht mal 2 und mal 4.
Man benutzt keine Abkürzungen, wenn man Password meint schreibt man nicht pw.
Bei SQL-SELECT benutzt man kein * sondern gibt die Felder explizit an. md5 benutzt man seit Anfang des Jahrtausends nicht mehr für Passwörter, hätte man eigentlich nie tun sollen.
Man würde eigentlich hexdigest benutzen, weil Binärdaten in Datenbanken problematisch sein könnte. Das zweite Skript scheint WSGI zu benutzen.
Das `print` zum Schluß ist Quatsch, weil es die Klasse Beitrag ausgibt.

Am besten vergisst Du das alles, und liest Dich in Flask ein.
Valentin KS
User
Beiträge: 10
Registriert: Samstag 12. Dezember 2020, 14:28
Wohnort: Kassel

Danke für die schnelle Antwort,
Sirius3 hat geschrieben: Donnerstag 29. Juli 2021, 22:35 Wenn Du ein Buch hast, das Dir CGI beibringen will, dann taugt es nur dazu, dass Dein Tisch nicht wackelt.
nein, auch dafür nicht, mit fast 1.000 Seiten deutlich zu dick.

Mir ist schon öfter aufgefallen dass das Buch nicht wirklich gut ist. :cry:
Das mit den Einzügen ist mir bekannt, die habe ich dann wohl übersehen, es gab auch 3er und 7er. :(
md5. Jaa ich weiß. In dem Buch heißt es auch das es nicht möglich ist in vernünftiger Zeit mit md5 eine Kollision zu erzeugen. :lol: :lol:
(als ob 30 min lange wäre)

Danke für die übrigen Hilfen und Korrekturen. Hatte halt ernsthaft gehofft ich könnte irgendwie das bisherige noch retten. :?
Werde mir json und Flask mal ansehen.
Antworten