Web-GUI bei Drucker umgehen

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Wir haben hier einen Netzwerkdrucker (bizhub c284) Für jedes Projekt wird eine Kostenstelle angelegt. Dies erfolgt über das Webinterface indem man gefühlte 700 Klicks machen muss, bis man an der Stelle angekommen ist, wo man die Kostenstelle anlegen kann.

Meine Idee wäre – sofern überhaupt möglich – hier Python einzusetzen. Ich hab mal ein wenig mit Requests gespielt.

Begonnen habe ich bei http://10.0.2.201/wcd/login.xml nach Anmeldung geht es über http://10.0.2.201/wcd/login.cgi zu http://10.0.2.201/wcd/a_authentication_track.xml und so weiter...

Code: Alles auswählen

import urllib
import urllib2

mydata=[('func','PSL_LP1_LOG'),('password','1234567812345678')]
mydata=urllib.urlencode(mydata)
path='http://10.0.2.201/wcd/login.cgi'
req=urllib2.Request(path, mydata)
req.add_header("Accept", "text/html")
page=urllib2.urlopen(req).read()

print page
Spuckt folgendes aus:

Code: Alles auswählen

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<meta http-equiv="Expires" content="0">
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta http-equiv="Refresh" content="0;URL=./a_system_counter.xml">
<meta http-equiv="Pragma" content="no-cache">
</head>
<body>
<script type="text/javascript"><!--
document.cookie="adm=AS_COU";document.cookie="access=";// --></script>
</body>
</html>
Wäre dies in etwa der richtige Weg? Wenn ja, hätte mir jemand ein paar Tipps, welche Module ich mir noch genauer anschauen muss bzw. ich ich weiter vorzugehen habe?

PS:
Ich hab vor über einer Woche Konica Minolta angeschrieben, ob die eine API hätten/zur Verfügung stellen könnten. Jedoch blieb die Antwort aus.
BlackJack

@lackschuh: Ich würde eher `mechanize` oder `requests` verwenden.
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

@lackschuh: das übliche Vorgehen ist, mit den Entwicklertools Deines Browsers den tatsächlichen Netzwerkverkehr anzuschauen: wie werden welche Formulardaten an welche URL geschickt? Plus den Cookie der die Session-ID speichert.
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Sirius3 hat geschrieben:@lackschuh: das übliche Vorgehen ist, mit den Entwicklertools Deines Browsers den tatsächlichen Netzwerkverkehr anzuschauen: wie werden welche Formulardaten an welche URL geschickt? Plus den Cookie der die Session-ID speichert.
Das hab ich schon gemacht mit Tamper Data. Das Ergebnis ist eine 4000 Zeilen lange XML Datei mit Hunderten von Cookies etc. Da muss ich zuerst mal den Überblick bekommen :?
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo

Ich bräuchte jetzt doch ein wenig Hilfe. Und zwar bin ich soweit 'vorgedrungen', so dass ich kurz davor bin, ein neues Projekt anzulegen (siehe Bild)

http://10.0.2.201/wcd/a_authentication_track.xml
Bild

Aber leider bringt mich dieser Button "Neue Registrierung" um den Verstand. Der Quellcode ist xml und wenn ich mittels "Entwickler-Tool" von Chrome den Button untersuche, dann wird mir folgendes angezeigt:

Code: Alles auswählen

<input type="button" value="Neue Registrierung" id="TrackNewRegist" onclick="html_f.setAu('TrackNew','AA_TRA');html_f.RESET('AA_TRA_Clearnew');html_f.hideHelpWindow();">
Nach dem Drücken des Buttons erscheint eine Eingabemaske. Die URL verändert sich aber nicht...

Wie könnte ich nun weiter vorgehen?

mfg


PS:

Code: Alles auswählen

import requests

payload = {'func': 'PSL_LP1_LOG', 'password': '1234567812345678'}

cookie = {}

r = requests.post('http://10.0.2.201/wcd/login.cgi', data=payload)

r2 = requests.post('http://10.0.2.201/wcd/a_system_counter.xml', cookies=r.cookies)
# print r2.text

r3 = requests.post('http://10.0.2.201/wcd/a_authentication_method.xml', cookies=r.cookies)
# print r3.text

r4 = requests.post('http://10.0.2.201/wcd/a_authentication_track.xml', cookies=r.cookies)
#  
print r4.text

Das wäre der letzte Teil, wo die Daten abgeschickt werden:

Code: Alles auswählen

<tdRequest uri="http%3A//10.0.2.201/wcd/a_user.cgi"><tdStartTime>16:05:30.517</tdStartTime>
<tdStartTimeMS>1383750330517</tdStartTimeMS>
<tdElapsedTime>704</tdElapsedTime>
<tdTotalElapsedTime>1226</tdTotalElapsedTime>
<tdStatus>200</tdStatus>
<tdStatusText>OK</tdStatusText><tdRequestMethod>POST</tdRequestMethod>
<tdContentSize>869</tdContentSize>
<tdMimeType>text/xml</tdMimeType>
<tdRequestHeaders>
<tdRequestHeader name="Host">
10.0.2.201</tdRequestHeader>
<tdRequestHeader name="User-Agent">
Mozilla/5.0%20%28Windows%20NT%206.1%3B%20WOW64%3B%20rv%3A25.0%29%20Gecko/20100101%20Firefox/25.0</tdRequestHeader>
<tdRequestHeader name="Accept">
text/html%2Capplication/xhtml+xml%2Capplication/xml%3Bq%3D0.9%2C*/*%3Bq%3D0.8</tdRequestHeader>
<tdRequestHeader name="Accept-Language">
de-de%2Cde%3Bq%3D0.8%2Cen-us%3Bq%3D0.5%2Cen%3Bq%3D0.3</tdRequestHeader>
<tdRequestHeader name="Accept-Encoding">
gzip%2C%20deflate</tdRequestHeader>
<tdRequestHeader name="Referer">
http%3A//10.0.2.201/wcd/a_authentication_track.xml</tdRequestHeader>
<tdRequestHeader name="Cookie">
bv%3DFirefox/25.0%3B%20uatype%3DNN%3B%20selno%3DDe%3B%20lang%3DDe%3B%20favmode%3Dfalse%3B%20vm%3DHtml%3B%20usr%3DS_INF%3B%20access%3D%3B%20param%3D%3B%20wd%3Dn%3B%20help%3Doff%2Coff%2Coff%3B%20adm%3DAA_TRA%3B%20ID%3D503e7c5ac78c9b314d77ecb4abf0989d</tdRequestHeader>
<tdRequestHeader name="Connection">
keep-alive</tdRequestHeader>
</tdRequestHeaders><tdPostHeaders><tdPostHeader name="Content-Type">
application/x-www-form-urlencoded
</tdPostHeader>
<tdPostHeader name="Content-Length">
291
</tdPostHeader>
</tdPostHeaders>
<tdPostElements>
<tdPostElement name="func">
PSL_AA_TRA_TRA
</tdPostElement>
<tdPostElement name="h_token">
aa892267191ef4e4a780536eebc1bd6846bdde1cdc29cab03e47820c01fd8559
</tdPostElement>
<tdPostElement name="AA_TRA_H_NUM">
new
</tdPostElement>
<tdPostElement name="AA_TRA_R_RNM">
Space
</tdPostElement>
<tdPostElement name="AA_TRA_P_UP">
12345678
</tdPostElement>
<tdPostElement name="AA_TRA_P_CMP">
12345678
</tdPostElement>
<tdPostElement name="AA_TRA_T_INF">
Test
</tdPostElement>
<tdPostElement name="AA_TRA_S_ACS">
Off
</tdPostElement>
<tdPostElement name="AA_TRA_S_FCP">
All
</tdPostElement>
<tdPostElement name="AA_TRA_S_FSC">
All
</tdPostElement>
<tdPostElement name="AA_TRA_S_FFA">
All
</tdPostElement>
<tdPostElement name="AA_TRA_S_FPR">
All
</tdPostElement>
<tdPostElement name="AA_TRA_S_FPS">
All
</tdPostElement>
</tdPostElements>
<tdResponseHeaders><tdResponseHeader name="Content-Type">
text/xml
[/size]
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo lackschuh,
letztlich ist es egal, was als Javascript aufgerufen wird, entscheidend ist, was letztlich gepostet wird.
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Sirius3 hat geschrieben:Hallo lackschuh,
letztlich ist es egal, was als Javascript aufgerufen wird, entscheidend ist, was letztlich gepostet wird.
Morgen Sirius3

Hab da noch ein paar Fragen. Und zwar gibt es da bei den PostData ein Element namens h_token, welches anscheinend dynamisch generiert wird bzw. dessen Wert (siehe unten). Leider weiss ich nicht, was das bedeutet.

Die zweite 'Bildungslücke' ist folgendes. Ich gehe von der URL http://10.0.2.201/wcd/a_authentication_track.xml aus (da, wo die Daten abgeschickt werden). Die Request URL ist dann http://10.0.2.201/wcd/a_user.cgi. Wie in etwa müsste dann der POST Request ausschauen?

Code: Alles auswählen

payload2 = {
           'func' : 'PSL_AA_TRA_TRA',
           'h_token' : 'aa892267191ef4e4a780536eebc1bd6846bdde1cdc29cab03e47820c01fd8559', #???
           'AA_TRA_H_NUM' : 'new',
           'AA_TRA_R_RNM' : 'Space',
           'AA_TRA_P_UP' : '12345678', # Projektnummer
           'AA_TRA_P_CMP' : '12345678', # Wiederholung Projektnummer
           'AA_TRA_T_INF' : 'Test v2', # Projektname
           'AA_TRA_S_ACS' : 'Off',
           'AA_TRA_S_FCP' : 'All',
           'AA_TRA_S_FSC' : 'All',
           'AA_TRA_S_FFA' : 'All',
           'AA_TRA_S_FPR' : 'All',
           'AA_TRA_S_FPS' : 'All'
           }
r4 = requests.post('http://10.0.2.201/wcd/a_authentication_track.xml', data=payload2)
[/size]

Code: Alles auswählen

Request URL:http://10.0.2.201/wcd/a_user.cgi
Request Method:POST
Status Code:200 OK
Request Headersview source
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:de,en-US;q=0.8,en;q=0.6,de-CH;q=0.4,de-AT;q=0.2
Connection:keep-alive
Content-Length:307
Content-Type:application/x-www-form-urlencoded
Cookie:ID_F=4e377857a9ea0bb83f35211eca2a4b49; wd=n; uatype=NN; selno=De; lang=De; param=; usr=ACO; bv=Chrome/30.0.1599.101; help=on,on,on; ID=804dd0f2520453ffee87e73ea5c1a70f; access=; vm=Html; favmode=false; adm=AA_TRA
Host:10.0.2.201
Origin:http://10.0.2.201
Referer:http://10.0.2.201/wcd/a_authentication_track.xml
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
Form Dataview sourceview URL encoded
func:PSL_AA_TRA_TRA
h_token:327f59f3db2da665224cc65213fea4b65edd1c68c5adf25d03b1501a22f2cc1d
AA_TRA_H_NUM:new
AA_TRA_R_RNM:Space
AA_TRA_P_UP:1234567812345678
AA_TRA_P_CMP:1234567812345678
AA_TRA_T_INF:Test
AA_TRA_S_ACS:Off
AA_TRA_S_FCP:All
AA_TRA_S_FSC:All
AA_TRA_S_FFA:All
AA_TRA_S_FPR:All
AA_TRA_S_FPS:All
Response Headersview source
Content-Length:869
Content-Type:text/xml
Expires:0
[/size]

Vielen Dank und ein schönes Wochenende :wink:
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo lackschuh,
Du mußt den Request an die Request-URL richten. Woher das Formular stammt ist ja letztlich egal. Steht der 'h_token' schon irgendwo im vom a_authentication_track.xml gesendeten HTML oder wird der per Javascript erzeugt?
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Der h_token wird nach Aufruf der a_authentication_track.xml geladen/erzeugt:

<Token>cb33a72acbf9d7c7197905ed0dfe55595d0605395b337087f5e8408dd3d7ea8f</Token>

Dann wäre also für mich zuerst der nächste Schritt, mich mit dem Modul xml auseinander zu setzen und ggf. das Element mit getElementsByTagName zubekommen?

EDIT:

Es geht nun :) Der Tipp mit dem Token war Gold wert. Das WE ist gerettet. Danke für die Hilfe.
Anbei noch der ganze Code:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
from xml.dom.minidom import parseString

payload = {
           'func': 'PSL_LP1_LOG',
           'password': '1234567812345678'
           }

r = requests.post('http://10.0.2.201/wcd/login.cgi', data=payload)

r2 = requests.post('http://10.0.2.201/wcd/a_system_counter.xml', cookies=r.cookies)
r3 = requests.post('http://10.0.2.201/wcd/a_authentication_method.xml', cookies=r.cookies)
r4 = requests.post('http://10.0.2.201/wcd/a_authentication_track.xml', cookies=r.cookies)
#  
print r4.text

token = r4.text
dom = parseString(token)
xmlTag = dom.getElementsByTagName('Token')[0].toxml()
xmlData=xmlTag.replace('<Token>','').replace('</Token>','')

print xmlTag
print xmlData

payload2 = {
           'func' : 'PSL_AA_TRA_TRA',
           'h_token' : xmlData,
           'AA_TRA_H_NUM' : 'new',
           'AA_TRA_R_RNM' : 'Space',
           'AA_TRA_P_UP' : '123456789', # Projektnummer
           'AA_TRA_P_CMP' : '123456789', # Wiederholung Projektnummer
           'AA_TRA_T_INF' : 'Test 12345', # Projektname
           'AA_TRA_S_ACS' : 'Off',
           'AA_TRA_S_FCP' : 'All',
           'AA_TRA_S_FSC' : 'All',
           'AA_TRA_S_FFA' : 'All',
           'AA_TRA_S_FPR' : 'All',
           'AA_TRA_S_FPS' : 'All'
           }

r5 = requests.post('http://10.0.2.201/wcd/a_user.cgi', data=payload2, cookies=r.cookies)
print r5.text
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

@lackschuh: die Requests für r2 und r3 sind wahrscheinlich überflüssig, da Du mit dem Ergebnis nichts machst. r4 ist dann ein get und kein post. Ich kenn mich mit minidom nicht so aus (ich würde ja ElementTree mit xmlData=dom.findtext('Token') nehmen) aber es gibt sicher eine Methode die direkt den TextContent zurückliefert und die unschönen "replace" überflüssig macht.
BlackJack

@lackschuh: Die Rückgabewerte von den `requests`-Aufrufen durchzunummerieren ist unschön. Warum musst Du die denn überhaupt alle aufheben?

Wenn Du ein `requests.Session`-Objekt verwendest, brauchst Du die Cookies nicht manuell verwalten.

Ich würde auch die `ElementTree`-API der DOM-API vorziehen. Und dann dieses hässliche ersetzen auf XML-Quelltext um an einen Wert zu kommen, weglassen.
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

@BlackJack

So besser: :wink:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import xml.etree.ElementTree as ET

payload = {
           'func': 'PSL_LP1_LOG',
           'password': '1234567812345678'
           }

r = requests.post('http://10.0.2.201/wcd/login.cgi', data=payload)
r2 = requests.post('http://10.0.2.201/wcd/a_authentication_track.xml', cookies=r.cookies)

root = ET.fromstring(r2.text)
for i in root.findall('Token'):
    xmlData = i.text

payload2 = {
           'func' : 'PSL_AA_TRA_TRA',
           'h_token' : xmlData,
           'AA_TRA_H_NUM' : 'new',
           'AA_TRA_R_RNM' : 'Space',
           'AA_TRA_P_UP' : '123456789', # Projektnummer
           'AA_TRA_P_CMP' : '123456789', # Wiederholung Projektnummer
           'AA_TRA_T_INF' : 'Test 123456', # Projektname
           'AA_TRA_S_ACS' : 'Off',
           'AA_TRA_S_FCP' : 'All',
           'AA_TRA_S_FSC' : 'All',
           'AA_TRA_S_FFA' : 'All',
           'AA_TRA_S_FPR' : 'All',
           'AA_TRA_S_FPS' : 'All'
           }
 
r3 = requests.post('http://10.0.2.201/wcd/a_user.cgi', data=payload2, cookies=r.cookies)
print r3.text

#Logout Funktion hinzufüegen
Request 2 und 3 brauch ich nicht. R4 brauch ich für das Token. Das mit den manuell verwalteten Cookies versteh ich nicht ganz... :?:
BlackJack

@lackschuh: Hör mal bitte auf Namen zu nummerieren. Im Grunde brauchst Du keines der `response`-Objekte nach der nächsten Anfragen. Nur von der ersten Antwort brauchst Du (noch) die Cookies. Das ist aber kein Grund das ganze Antwort-Objekt aufheben zu müssen. Das gleiche gilt für `payload`.

Mit manuell die Cookies verwalten meine ich das Du manuell, also selbst, mit Code, den Du geschrieben hast, Dich immer wieder um die Cookies bei jeder Anfragen kümmerst. Dafür gibt es `requests.Session`. Einmal erstellen, darüber die Anfragen stellen, nicht selber um Cookies kümmern.

Bei dem XML solltest Du das `content`-Attribut statt des `text` von der Antwort verwenden, denn dieses XML ist kein Text sondern besteht aus Bytes. Das dekodieren erledigt der XML-Parser.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8
import requests
import xml.etree.ElementTree as etree

BASE_URL = 'http://10.0.2.201/wcd/'


def main():
    with requests.Session() as session:
        data = {'func': 'PSL_LP1_LOG', 'password': '1234567812345678'}
        session.post(BASE_URL + 'login.cgi', data=data)

        response = session.get(BASE_URL + 'a_authentication_track.xml')
        token = etree.fromstring(response.content).find('.//Token').text

        project_id = '123456789'
        project_name = 'Test 123456'
        data = {
           'func': 'PSL_AA_TRA_TRA',
           'h_token': token,
           'AA_TRA_H_NUM': 'new',
           'AA_TRA_R_RNM': 'Space',
           'AA_TRA_P_UP': project_id,
           'AA_TRA_P_CMP': project_id,
           'AA_TRA_T_INF': project_name,
           'AA_TRA_S_ACS': 'Off',
           'AA_TRA_S_FCP': 'All',
           'AA_TRA_S_FSC': 'All',
           'AA_TRA_S_FFA': 'All',
           'AA_TRA_S_FPR': 'All',
           'AA_TRA_S_FPS': 'All',
        }
        response = requests.post(BASE_URL + 'a_user.cgi', data=data)
        print response.text
         
        # TODO Logout Funktion hinzufügen
 
 
if __name__ == '__main__':
    main()
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Morgen,

Danke für die Tipps. Hab nun auch das mit requests.Session() begriffen. Ein wenig aufmerksamer in der Doku lesen und man würde
Session Objects
The Session object allows you to persist certain parameters across requests. It also persists cookies across all requests made from the Session instance.
finden 8)

Abschließend hab ich noch ein letztes Problem, welches ich sehr wahrscheinlich auf die unsauberste Art gelöst habe. Und zwar soll eine Meldung erscheinen, falls ein Projekt bereits erstellt wurde:

Code: Alles auswählen

        #Fehlermeldung, falls Projekt bereits besteht
        try:
            token = etree.fromstring(response.content).find('.//ErrorDescription').text
            if token == "Don't set Duplicate Password":
                print 'ERROR!', 'Project %s existiert bereits!' %project_id
        except AttributeError:
            print 'Job Done!', project_name, project_id 
            
        
        # Logout
        data = {'func': 'PSL_ACO_LGO'}
        session.post('http://10.0.2.201/wcd/a_user.cgi', data=data)
zB seht hier im Wiki, dass man generell einen Codeblock nicht in try...except packen soll. Leider fällt mir gerade nichts anders ein...
In mienem Beispielt verhält es sich so, dass, wenn ein Projekt erfolgreich angelegt wurde auch keine Meldung (Don't set Duplicate Password) im Quelltext erscheint und so das Attribut "token" auch kein Wert zugewiesen bekommt, wobei der Attributname sowiso falsch bennant wurde... ggf pw_error oder so bennenen.
mfg
BlackJack

@lackschuh: Hier könnte man das Ergebnis von dem `find()` an einen Namen binden und auf `None` prüfen. Falls es `None` ist, war alles in Ordnung, ansonsten gab es eine Fehlerbeschreibung im XML. Das sollte man vielleicht auch an den Benutzer melden falls es nicht der erwartete Fehler war. Zusammen mit dem Fehlertext. Ansonsten könnten an der Stelle Fehler verschluckt werden die der Benutzer nie zu gesicht bekommt.

Eventuell macht es auch Sinn eine generelle Funktion oder Methode zum Aufruf von „Funktionen” des Druckers zu schreiben, die wenn eine Fehlerbeschreibung im XML gefunden wird, eine Ausnahme daraus erstellt und auslöst.
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

@lackschuh: oder nimm gleich findtext. Das liefert auch None, wenn der Eintrag nicht gefunden wird.
Antworten