Seite 1 von 2

POST-Daten via Purebasic zu Webseite im Browser schicken

Verfasst: 08.12.2015 14:22
von NicTheQuick
Hallo Leute,

ich brauchte die Möglichkeit eine Webseite im Standardbrowser aufzurufen und dieser Daten per POST mitzugeben. Da man die Daten nicht einfach an die URL anhängen kann und damit den Standardbrowser mit Runprogram() aufrufen kann, musste ich eine Zwischenschicht bauen, die ich mir ein wenig von Dropbox abgeschaut habe.
Im Grunde wird eine temporäre HTML-Datei erstellt, die die zu übergebenden Daten verschlüsselt enthält und nach dem Entschlüsseln an die eigentliche Webseite weitergibt, indem sie eine HTML-Form submitted. Der Schlüssel zum Entschlüsseln wird als Hash am Ende der URL übergeben und während der Skriptausführung entfernt, sodass in der Browser-History keine Rückstände bleiben.
Damit der Purebasic-Code möglichst kompakt bleibt, habe ich das Template der HTML-Datei möglichst klein gehalten, d.h. auch den JS-Code minimiert.

Um den Code direkt testen zu können, habe ich ein kleines Skript auf meinem Webspace liegen, dass POST- und GET-Daten anzeigt. Hier nun der Code:
Aktuell Windows/Linux only und PB 5.41 beta 2

Code: Alles auswählen

EnableExplicit

DeclareModule BrowserPost
	EnableExplicit
	
	Declare.i openURL(actionUrl.s, Map postData.s(), title.s = "", nojsUrl.s = "", nojsInfo.s = "", keyLength.i = 64)
	
	Declare.i removeTempFiles(deltaTime.i = -1)
EndDeclareModule

Module BrowserPost
	EnableExplicit
	
	Structure TFiles
		time.i
		file.s
	EndStructure
	
	Global NewList tFiles.TFiles()
	Global tfilesLock.i = CreateMutex()
	
	; Konvertiert text.s zu Unicode, verschlüsselt die Rohdaten mit dem Schlüssel und
	; konvertiert die Daten zu einem Hex-String.
	Procedure.s convert2Hex(text.s, *key, keyLength.i, *length.Integer = 0)
		Protected dataSize.i = StringByteLength(text, #PB_Unicode) + SizeOf(Unicode)
		Protected *buffer = AllocateMemory(dataSize)
		PokeS(*buffer, text, -1, #PB_Unicode)
		
		Protected hex.s = "", *i.Unicode = *buffer, *k.Unicode = *key
		
		While *i\u
			hex + RSet(Hex(*i\u ! *k\u, #PB_Unicode), 4, "0")
			*i + SizeOf(Unicode)
			*k + SizeOf(Unicode)
			If (*k - *key) >= keyLength
				*k - keyLength
			EndIf
		Wend
		
		FreeMemory(*buffer)
		
		If *length
			*length\i = dataSize - SizeOf(Unicode)
		EndIf
		
		ProcedureReturn hex
	EndProcedure
	
	CompilerIf #PB_Compiler_OS = #PB_OS_Windows
		; ts-soft: http://www.purebasic.fr/german/viewtopic.php?p=282092#p282092
		Procedure.s FindAssociatedProgram(File.s)
			Protected Result.s = Space(#MAX_PATH)
			Protected Error
			
			Error = FindExecutable_(@File, 0, @Result)
			If Error <= 32
				ProcedureReturn ""
			EndIf
			ProcedureReturn Result
		EndProcedure
	CompilerElse
		Procedure.s FindAssociatedProgram(File.s)
			ProcedureReturn "x-www-browser"
		EndProcedure
	CompilerEndIf
	
	Procedure openURL(actionUrl.s, Map postData.s(), title.s = "", nojsUrl.s = "", nojsInfo.s = "", keyLength.i = 64)
		If Not (keyLength % 2 = 0)
			ProcedureReturn #False
		EndIf
		
		; Erzeuge Schlüssel
		Protected *key = AllocateMemory(keyLength)
		If Not *key
			ProcedureReturn #False
		EndIf
		
		If Not OpenCryptRandom()
			ProcedureReturn #False
		EndIf
		CryptRandomData(*key, keyLength)
		
		Protected i.i, hexKey.s = ""
		For i = 0 To keyLength - 1 Step 2
			hexKey + RSet(Hex(PeekU(*key + i), #PB_Unicode), 4, "0")
		Next
		
		; Wandle Post Data in JSON-String um.
		Protected json.i = CreateJSON(#PB_Any)
	    Protected jsonData.i = SetJSONObject(JSONValue(json))
	    ForEach postData()
	    	SetJSONString(AddJSONMember(jsonData, MapKey(postData())), postData())
	    Next
		Protected jsonStr.s = ComposeJSON(json)
		FreeJSON(json)
		
		; Wandle JSON-String in Hex-String um.
		Protected jsonLength.i, jsonHex.s = convert2Hex(jsonStr, *key, keyLength, @jsonLength)
		; Wandle Länge des JSON-String in HEX um.
		Protected.s hexLength = RSet(Hex(jsonLength * 2, #PB_Long), 8, "0")
		
		; Gib Schlüssel wieder frei
		FillMemory(*key, keyLength, 0)
		FreeMemory(*key)
		
		; Erstelle Mark of the Web
		Protected motw.s = GetURLPart(actionUrl, #PB_URL_Protocol) + "://" + GetURLPart(actionUrl, #PB_URL_Site)
		
		; Lies HTML-Template aus Datasection aus.
		Protected template.s = PeekS(?template_begin2, -1, #PB_Unicode) ;PeekS(?template_begin, -1, #PB_UTF8)
		
		; Ersetze Variablen in Template.
		template = ReplaceString(template, "%MOTW%", "(" + RSet(Str(Len(motw)), 4, "0") + ")" + motw)
		template = ReplaceString(template, "%TITLE%", title)
		template = ReplaceString(template, "%ACTION%", actionUrl)
		template = ReplaceString(template, "%POSTDATA%", jsonHex)
		template = ReplaceString(template, "%NOJSACTION%", nojsUrl)
		template = ReplaceString(template, "%NOJSINFO%", nojsinfo)
		
		; Suche zufälligen nicht existenten Dateinamen im temporären Verzeichnis
		Repeat
			Protected tempFile.s = GetTemporaryDirectory() + "_post" + Hex(Random(2147483647), #PB_Long) + "-" + Hex(Random(2147483647), #PB_Long) + ".html"
		Until FileSize(tempFile) = -1
		
		; Schreibe Template in Datei
		Protected fileId.i = CreateFile(#PB_Any, tempFile)
		If Not fileId
			ProcedureReturn #False
		EndIf
		WriteString(fileId, template, #PB_UTF8)
		CloseFile(fileId)
		LockMutex(tfilesLock)
		If AddElement(tFiles())
			tFiles()\file = tempFile
			tfiles()\time = Date()
		EndIf
		UnlockMutex(tfilesLock)
		
		; Suche Standardprogramm zum Öffnen von HTML-Dateien.
		Protected browser.s = FindAssociatedProgram(tempFile)
		If browser = ""
			ProcedureReturn #False
		EndIf
		
		; Setze URL für den Browser zusammen
		Protected call.s = ~"\"file://" + tempFile + "#" + hexLength + hexKey + #DQUOTE$
		
		ProcedureReturn RunProgram(browser, call, "")
		
		DataSection
			template_begin:
				;IncludeBinary "OpenBrowserWithPostData.html"
				;Data.u 0
			template_begin2:
				Data.s ~"<!DOCTYPE html>\r\n<!-- saved from url=%MOTW% -->\r\n<html><head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" +
				       ~"<script type=\"text/javascript\">\r\n" +
				       "!function(){'use strict';function t(t,e){if(void 0==t||void 0==e||t.length%4!==0||e.length%4!==0||t.length<12)return null;" +
				       "var n=parseInt(t.slice(0,8),16);if(n!==e.length)return null;t=t.substr(8);for(var r=0,i='',o=0;o!==e.length;o+=4)i+=String." +
				       "fromCharCode(parseInt(t.slice(r,r+4),16)^parseInt(e.slice(o,o+4),16)),r=(r+4)%t.length;return i}function e(){var e=window." +
				       "location.hash.substr(1);window.location.hash='';var n=document.getElementsByName('data')[0],r=t(e,n.getAttribute('post-data'));" +
				       "if(document.submitPost.removeChild(n),null!=r){var i=JSON.parse(r);for(var o in i)if(i.hasOwnProperty(o)){var s=document." +
				       "createElement('input');s.setAttribute('type','hidden'),s.setAttribute('name',o),s.setAttribute('value',i[o]),document.submitPost." +
				       ~"appendChild(s)}document.submitPost.submit()}}window.onload=e}();\r\n" +
				       "</script><title>%TITLE%</title></head><body><form id='submitPost' name='submitPost' action='%ACTION%' method='post'>" +
				       "<input type='hidden' name='data' value='' post-Data='%POSTDATA%'><noscript><div class='center'>" +
				       ~"<meta id=\"meta-refresh\" http-equiv=\"refresh\" content=\"2;URL=%NOJSACTION%\"><p>%NOJSINFO%</p>" +
				       ~"<a href=\"%NOJSACTION%\">Link</a></div></noscript></form></body></html>"
				Data.u 0
				       
		EndDataSection
	EndProcedure
	
	Procedure.i removeTempFiles(deltaTime.i = -1)
		If deltaTime < 0
			ProcedureReturn ListSize(tFiles())
		EndIf
		LockMutex(tfilesLock)
		ResetList(tFiles())
		While NextElement(tFiles())
			If tFiles()\time + deltaTime <= Date()
				DeleteFile(tFiles()\file)
				DeleteElement(tFiles())
			EndIf
		Wend
		UnlockMutex(tfilesLock)
		
		ProcedureReturn ListSize(tFiles())
	EndProcedure
EndModule

NewMap postData.s()

postData("__ac_name") = "admin"
postData("__ac_password") = "herein"
postData("form.submitted") = "1"
postData("pwd_empty") = "0"
postData("js_enabled") = "0"

BrowserPost::openURL("http://freakscorner.de/test.php?bla=normales+GET", postData(), "Bastelkeller Weiterleitung", "http://freakscorner.de/", "Da in ihrem Browser Skripte deaktiviert wurden, können die Daten nicht automatisch übertragen werden.")

Delay(5000)
BrowserPost::removeTempFiles(0)
TODO:
  • Mac-Version
  • Automatisches Löschen der temporären Datei nach einer bestimmten Zeit.
test.php:

Code: Alles auswählen

<html>
	<body>
		<h3>POST</h3>
		<? var_dump($_POST); ?>
		<h3>GET</h3>
		<? var_dump($_GET); ?>
		<h3>REQUEST</h3>
		<? var_dump($_REQUEST); ?>
	</body>
</html>
Nicht minimiertes Template:

Code: Alles auswählen

<!DOCTYPE html>
<!-- saved from url=%MOTW% -->
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script type="text/javascript">
        (function () {
            "use strict";
            function decryptData(key, data) {
                if (key == undefined || data == undefined || (key.length % 4) !== 0 || (data.length % 4) !== 0 || key.length < 12) {
                    return null;
                }
				var dataLength = parseInt(key.slice(0, 8), 16);
				if (dataLength !== data.length) {
					return null;
				}
				key = key.substr(8);

                var j = 0, out = '';
                for (var i = 0; i !== data.length; i += 4) {
                    out += String.fromCharCode(parseInt(key.slice(j, j + 4), 16) ^ parseInt(data.slice(i, i + 4), 16));
					j = (j + 4) % key.length;
                }
                return out;
            }
            function go() {
                var hashKey = window.location.hash.substr(1);
                window.location.hash = '';
                var inputs = document.getElementsByName('data')[0];
				var jsonStr = decryptData(hashKey, inputs.getAttribute('post-data'));
				document.submitPost.removeChild(inputs);
				if (jsonStr == null) {
					return;
				}
				var jsonObj = JSON.parse(jsonStr);
				for (var key in jsonObj) {
					if (jsonObj.hasOwnProperty(key)) {
						var hiddenInput = document.createElement("input");
						hiddenInput.setAttribute("type", "hidden");
						hiddenInput.setAttribute("name", key);
						hiddenInput.setAttribute("value", jsonObj[key]);
						document.submitPost.appendChild(hiddenInput);
					}
				}
				document.submitPost.submit();
			}
            window.onload = go;
        })();
    </script>
    <title>%TITLE%</title>
</head>
<body>
<form id="submitPost" name="submitPost" action="%ACTION%" method="post">
    <input type="hidden" name="data" value="" post-data="%POSTDATA%">
    <noscript>
        <div class="center">
            <meta id="meta-refresh" http-equiv="refresh" content="2;URL=%NOJSACTION%">
            <p>%NOJSINFO%</p>
            <a href="%NOJSACTION%">Link</a>
        </div>
    </noscript>
</form>
</body>
</html>
Edit:
  • Linux-Version hinzugefügt
  • In Modul gepackt
  • Löschmöglichkeit eingebaut.

Re: POST-Daten via Purebasic zu Webseite im Browser schicken

Verfasst: 08.12.2015 14:54
von ts-soft
Kleiner Hinweis: Falls das "EnableExplicit" für das Module sein soll, würde ich es auch dort plazieren,
weil so wirkt es nur Ausserhalb :wink:

Re: POST-Daten via Purebasic zu Webseite im Browser schicken

Verfasst: 08.12.2015 15:08
von NicTheQuick
ts-soft hat geschrieben:Kleiner Hinweis: Falls das "EnableExplicit" für das Module sein soll, würde ich es auch dort plazieren,
weil so wirkt es nur Ausserhalb :wink:
Das hast du mir in irgendeinem anderen Thread auch schonmal gesagt. :lol: Aber mir war es bewusst. Falsch kann momentan nichts mehr sein, weil ich den vorhandenen Code nur noch ins Modul kopiert habe. Ich könnte es aber natürlich der Vollständigkeit halber gleich mal einbauen.

Re: POST-Daten via Purebasic zu Webseite im Browser schicken

Verfasst: 08.12.2015 15:29
von NeoChris
Is das nicht doppelt gemoppelt?! Einfach oben einmalig einfügen und gut is.

Wen jemand meint EnableExplicit nicht zu nutzen selbst schuld!

Re: POST-Daten via Purebasic zu Webseite im Browser schicken

Verfasst: 08.12.2015 15:44
von NicTheQuick
Eben nicht. Ein "EnableExplicit" oben reicht nicht. Innerhalb eines Modules musst du nochmal "EnableExplicit" nutzen, sonst wirkt es nicht.

Code: Alles auswählen

EnableExplicit
DeclareModule bla
	a.s = "hi"
EndDeclareModule
Module bla
EndModule

Re: POST-Daten via Purebasic zu Webseite im Browser schicken

Verfasst: 08.12.2015 15:57
von NeoChris
thx für die Info die mir nicht bekannt war. Nich gewusst.

Re: POST-Daten via Purebasic zu Webseite im Browser schicken

Verfasst: 08.12.2015 16:09
von ts-soft
PureBasic.chm - Module hat geschrieben:Wenn die Anweisungen Define, EnableExplicit, EnableASM in einem Modul verwendet werden, haben sie keine Wirkung außerhalb des jeweiligen Moduls, und umgekehrt.

Re: POST-Daten via Purebasic zu Webseite im Browser schicken

Verfasst: 08.12.2015 16:13
von NeoChris
Du bist zu langsam, Nic hats mir grad eben erzählt! ;)

Re: POST-Daten via Purebasic zu Webseite im Browser schicken

Verfasst: 09.12.2015 15:04
von funkheld
Das liest sich ja wunderbar, was dein Programm macht.

So etwas suche ich hier schon seit einpaar Tagen für meinem Yun.
Habe mir soeben das neueste Purebasic geladen.
Dein Programm funktioniert bei mir.

Nun möchte ich Daten damit zur http-seite senden zum Arduino Yun über http.
------------------------------------------------------------
text holen : "192.168.240.1/data/get/analog0"
Hier liegen Daten vom Arduino Yun.

text senden : "192.168.240.1/data/put/ledhigh/1"
hier wird eine 1 zum Arduino Yun gesendet.
-------------------------------------------------------------

Wo kann ich das bitte bei dir einsetzen , das ich Daten empfangen kann (get) und Daten senden kann (post) ?


Danke.
Gruss

Re: POST-Daten via Purebasic zu Webseite im Browser schicken

Verfasst: 09.12.2015 15:07
von Nino
Sehr interessantes Modul!
Zum Testen wollte ich mich automatisch hier im Forum anmelden.
Code:

Code: Alles auswählen

NewMap postData.s()

postData("username") = "Nino"
postData("password") = "<richtiges Passwort>"

BrowserPost::openURL("http://www.purebasic.fr/german/ucp.php?mode=login", postData(), "PureBoard-Anmeldung")

Delay(5000)
BrowserPost::removeTempFiles(0)
Die Feldnamen "username" und "password" habe ich mit Hilfe von Firefox' 42.0 "Inspektor" ermittelt.

Zwar wird die Anmeldeseite aufgerufen, aber mehr passiert nicht, alle Felder bleiben leer.
Geht das nicht mit diesem Modul, oder habe ich etwas falsch gemacht?