No-SMTP email sending. Send email from server with PHP (when local access to SMTP is blocked or not available)

Post your working scripts, libraries and tools
gabrieldane
Posts: 1
Joined: 22 May 2016, 22:47

No-SMTP email sending. Send email from server with PHP (when local access to SMTP is blocked or not available)

22 May 2016, 22:56

I put together this script pair (AHK/PHP) to solve a very specific problem I was having. We have an app that we want to add the ability to send email from, but SMTP ports are blocked on many of the PC's where it will be used. The solution was to use PHP mail() to do the sending.

The AHK script takes your email message contents, dumps them to JSON, and sends them via POST to a PHP script on your server.

The PHP script receives the connection, decodes the JSON, and uses mail() to send the message.

No SMTP is required. I hope someone else will find this helpful if they run across a similar use case need.
-Gabe

Code: Select all

;AHK variables with email content
myEmail := "[email protected]"
myFriend := "[email protected]"
mySubject := "My subject"
myMessage := "My message."


;use JSON Dump
test_upload := JSON.Dump({fromEmail:myEmail,toEmail:myFriend,emailSubject:mySubject,emailMessage:myMessage},"  ")


;urlencode JSON
test_upload := urlencode(test_upload)


try {
	;upload mail to PHP
	pserver := "http://myserver.com/AHK_PHP_mail.php"
	whr := ComObjCreate("WinHttp.WinHttpRequest.5.1")
	whr.Open("POST",pserver, false)
	whr.SetRequestHeader("Content-Type","application/x-www-form-urlencoded")
	whr_request := "obj=" test_upload
	whr.Send(whr_request)
	whr.WaitForResponse()
	response := whr.ResponseText
	
	;MsgBox Response:`n%response%`n`n- Remove this its for debugging
	Msgbox, ,Success ,Your message has been sent to %myFriend%!

}
catch e
{
	Msgbox, Error caught
}


return


/*
	Functions
	- urlencode
	- JSON support functions
*/

urlencode(str){
	oldformat := A_FormatInteger
	setformat, integer, H
	loop, parse, str
	{
		if A_LoopField is alnum
		{
			out .= A_LoopField
			continue
		}
		hex := substr(asc(A_LoopField),3)
		out .= "%" . (strlen(hex) = 1 ? "0" . hex : hex)
	}
	setformat, integer, %oldformat%
	return out
}

/* 
	JSON handling code
*/
class JSON
{
	/* Method: Load
	 *     Deserialize a string containing a JSON document to an AHK object.
	 * Syntax:
	 *     json_obj := JSON.Load( ByRef src [ , jsonize := false ] )
	 * Parameter(s):
	 *     src  [in, ByRef] - String containing a JSON document
	 *     jsonize     [in] - If true, objects {} and arrays [] are wrapped as
	 *                        JSON.Object and JSON.Array instances respectively.
	 */
	Load(ByRef src, jsonize:=false)
	{
		static q := Chr(34)

		args := jsonize ? [ JSON.Object, JSON.Array ] : []
		key := "", is_key := false
		stack := [ tree := [] ]
		is_arr := { (tree): 1 }
		next := q . "{[01234567890-tfn"
		pos := 0
		while ( (ch := SubStr(src, ++pos, 1)) != "" )
		{
			if InStr(" `t`n`r", ch)
				continue
			if !InStr(next, ch)
			{
				ln  := ObjLength(StrSplit(SubStr(src, 1, pos), "`n"))
				col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1))

				msg := Format("{}: line {} col {} (char {})"
				,   (next == "")      ? ["Extra data", ch := SubStr(src, pos)][1]
				  : (next == "'")     ? "Unterminated string starting at"
				  : (next == "\")     ? "Invalid \escape"
				  : (next == ":")     ? "Expecting ':' delimiter"
				  : (next == q)       ? "Expecting object key enclosed in double quotes"
				  : (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'"
				  : (next == ",}")    ? "Expecting ',' delimiter or object closing '}'"
				  : (next == ",]")    ? "Expecting ',' delimiter or array closing ']'"
				  : [ "Expecting JSON value(string, number, [true, false, null], object or array)"
				    , ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1]
				, ln, col, pos)

				throw Exception(msg, -1, ch)
			}
			
			is_array := is_arr[obj := stack[1]]
			
			if i := InStr("{[", ch)
			{
				val := (proto := args[i]) ? new proto : {}
				is_array? ObjPush(obj, val) : obj[key] := val
				ObjInsertAt(stack, 1, val)
				
				is_arr[val] := !(is_key := ch == "{")
				next := q . (is_key ? "}" : "{[]0123456789-tfn")
			}

			else if InStr("}]", ch)
			{
				ObjRemoveAt(stack, 1)
				next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}"
			}

			else if InStr(",:", ch)
			{
				is_key := (!is_array && ch == ",")
				next := is_key ? q : q . "{[0123456789-tfn"
			}

			else
			{
				if (ch == q)
				{
					i := pos
					while i := InStr(src, q,, i+1)
					{
						val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C")
						static end := A_AhkVersion<"2" ? 0 : -1
						if (SubStr(val, end) != "\")
							break
					}
					if !i ? (pos--, next := "'") : 0
						continue
					
					pos := i ; update pos

					  val := StrReplace(val,    "\/",  "/")
					, val := StrReplace(val, "\" . q,    q)
					, val := StrReplace(val,    "\b", "`b")
					, val := StrReplace(val,    "\f", "`f")
					, val := StrReplace(val,    "\n", "`n")
					, val := StrReplace(val,    "\r", "`r")
					, val := StrReplace(val,    "\t", "`t")

					i := 0
					while (i := InStr(val, "\",, i+1))
					{
						if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0
							continue 2

						; \uXXXX - JSON unicode escape sequence
						xxxx := Abs("0x" . SubStr(val, i+2, 4))
						if (A_IsUnicode || xxxx < 0x100)
							val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6)
					}

					if is_key
					{
						key := val, next := ":"
						continue
					}
				}
				
				else
				{
					val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos)
					
					static null := "" ; for #Warn
					if InStr(",true,false,null,", "," . val . ",", true) ; if var in
						val := %val%
					else if (Abs(val) == "") ? (pos--, next := "#") : 0
						continue
					
					val := val + 0, pos += i-1
				}
				
				is_array? ObjPush(obj, val) : obj[key] := val
				next := obj==tree ? "" : is_array ? ",]" : ",}"
			}
		}
		
		return tree[1]
	}
	/* Method: Dump
	 *     Serialize an object to a JSON formatted string.
	 * Syntax:
	 *     json_str := JSON.Dump( obj [ , indent := "" ] )
	 * Parameter(s):
	 *     obj      [in] - The object to stringify.
	 *     indent   [in] - Specify string(s) to use as indentation per level.
 	 */
	Dump(obj:="", indent:="", lvl:=1)
	{
		static q := Chr(34)

		if IsObject(obj)
		{
			static Type := Func("Type")
			if Type ? (Type.Call(obj) != "Object") : (ObjGetCapacity(obj) == "") ; COM,Func,RegExMatch,File,Property object
				throw Exception("Object type not supported.", -1, Format("<Object at 0x{:p}>", &obj))
			
			is_array := 0
			for k in obj
				is_array := (k == A_Index)
			until !is_array

			static integer := "integer"
			if indent is %integer%
			{
				if (indent < 0)
					throw Exception("Indent parameter must be a postive integer.", -1, indent)
				spaces := indent, indent := ""
				Loop % spaces
					indent .= " "
			}
			indt := ""
			Loop, % indent ? lvl : 0
				indt .= indent

			lvl += 1, out := "" ; make #Warn happy
			for k, v in obj
			{
				if IsObject(k) || (k == "")
					throw Exception("Invalid object key.", -1, k ? Format("<Object at 0x{:p}>", &obj) : "<blank>")
				
				if !is_array
					out .= ( ObjGetCapacity([k], 1) ? JSON.Dump(k) : q . k . q ) ; key
					    .  ( indent ? ": " : ":" ) ; token + padding
				out .= JSON.Dump(v, indent, lvl) ; value
				    .  ( indent ? ",`n" . indt : "," ) ; token + indent
			}
			
			if (out != "")
			{
				out := Trim(out, ",`n" indent)
				if (indent != "")
					out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1)
			}
			
			return is_array ? "[" . out . "]" : "{" . out . "}"
		}
		
		; Number
		if (ObjGetCapacity([obj], 1) == "") ; returns an integer if 'obj' is string
			return obj
		
		; String (null -> not supported by AHK)
		if (obj != "")
		{
			  obj := StrReplace(obj,  "\",    "\\")
			, obj := StrReplace(obj,  "/",    "\/")
			, obj := StrReplace(obj,    q, "\" . q)
			, obj := StrReplace(obj, "`b",    "\b")
			, obj := StrReplace(obj, "`f",    "\f")
			, obj := StrReplace(obj, "`n",    "\n")
			, obj := StrReplace(obj, "`r",    "\r")
			, obj := StrReplace(obj, "`t",    "\t")

			static needle := (A_AhkVersion<"2" ? "O)" : "") . "[^\x20-\x7e]"
			while RegExMatch(obj, needle, m)
				obj := StrReplace(obj, m[0], Format("\u{:04X}", Ord(m[0])))
		}
		
		return q . obj . q
	}
	
	class Object
	{
		
		__New(args*)
		{
			if ((len := ObjLength(args)) & 1)
				throw Exception("Too few parameters passed to function.", -1, len)

			ObjRawSet(this, "_", []) ; bypass __Set
			Loop % len//2
				this[args[A_Index*2-1]] := args[A_Index*2] ; invoke __Set
		}

		__Set(key, args*)
		{
			ObjPush(this._, key) ; add key to key list and allow __Set to continue normally
		}

		Delete(FirstKey, LastKey*)
		{
			IsRange := ObjLength(LastKey)
			i := 0
			for index, key in ObjClone(this._)
				if IsRange ? (key >= FirstKey && key <= LastKey[1]) : (key = FirstKey)
				{
					ObjRemoveAt(this._, index - (i++))
					if !IsRange ; single key only
						break
				}
			
			return ObjDelete(this, FirstKey, LastKey*)
		}

		Dump(indent:="")
		{
			return JSON.Dump(this, indent)
		}
		static Stringify := JSON.Object.Dump

		_NewEnum()
		{
			static enum := { "Next": JSON.Object._EnumNext }
			return { base: enum, enum: ObjNewEnum(this._), obj: this }
		}

		_EnumNext(ByRef key, ByRef val:="")
		{
			if r := this.enum.Next(, key)
				val := this.obj[key]
			return r
		}
		; Do not implement array methods??
		static InsertAt := "", RemoveAt := "", Push := "", Pop := ""
	}
		
	class Array
	{
			
		__New(args*)
		{
			args.base := this.base
			return args
		}

		Dump(indent:="")
		{
			return JSON.Dump(this, indent)
		}
		static Stringify := JSON.Array.Dump
	}
	; Deprecated but maintained for existing scripts using the lib
	static Parse := JSON.Load ; cast to .Load
	static Stringify := JSON.Dump ; cast to .Dump
}

/*
	More JSON functions 
*/

json_toobj( str ) {

	quot := """" ; firmcoded specifically for readability. Hardcode for (minor) performance gain
	ws := "`t`n`r " Chr(160) ; whitespace plus NBSP. This gets trimmed from the markup
	obj := {} ; dummy object
	objs := [] ; stack
	keys := [] ; stack
	isarrays := [] ; stack
	literals := [] ; queue
	y := nest := 0

; First pass swaps out literal strings so we can parse the markup easily
	StringGetPos, z, str, %quot% ; initial seek
	while !ErrorLevel
	{
		; Look for the non-literal quote that ends this string. Encode literal backslashes as '\u005C' because the
		; '\u..' entities are decoded last and that prevents literal backslashes from borking normal characters
		StringGetPos, x, str, %quot%,, % z + 1
		while !ErrorLevel
		{
			StringMid, key, str, z + 2, x - z - 1
			StringReplace, key, key, \\, \u005C, A
			If SubStr( key, 0 ) != "\"
				Break
			StringGetPos, x, str, %quot%,, % x + 1
		}
	;	StringReplace, str, str, %quot%%t%%quot%, %quot% ; this might corrupt the string
		str := ( z ? SubStr( str, 1, z ) : "" ) quot SubStr( str, x + 2 ) ; this won't

	; Decode entities
		StringReplace, key, key, \%quot%, %quot%, A
		StringReplace, key, key, \b, % Chr(08), A
		StringReplace, key, key, \t, % A_Tab, A
		StringReplace, key, key, \n, `n, A
		StringReplace, key, key, \f, % Chr(12), A
		StringReplace, key, key, \r, `r, A
		StringReplace, key, key, \/, /, A
		while y := InStr( key, "\u", 0, y + 1 )
			if ( A_IsUnicode || Abs( "0x" SubStr( key, y + 2, 4 ) ) < 0x100 )
				key := ( y = 1 ? "" : SubStr( key, 1, y - 1 ) ) Chr( "0x" SubStr( key, y + 2, 4 ) ) SubStr( key, y + 6 )

		literals.insert(key)

		StringGetPos, z, str, %quot%,, % z + 1 ; seek
	}

; Second pass parses the markup and builds the object iteratively, swapping placeholders as they are encountered
	key := isarray := 1

	; The outer loop splits the blob into paths at markers where nest level decreases
	Loop Parse, str, % "]}"
	{
		StringReplace, str, A_LoopField, [, [], A ; mark any array open-brackets

		; This inner loop splits the path into segments at markers that signal nest level increases
		Loop Parse, str, % "[{"
		{
			; The first segment might contain members that belong to the previous object
			; Otherwise, push the previous object and key to their stacks and start a new object
			if ( A_Index != 1 )
			{
				objs.insert( obj )
				isarrays.insert( isarray )
				keys.insert( key )
				obj := {}
				isarray := key := Asc( A_LoopField ) = 93
			}

			; arrrrays are made by pirates and they have index keys
			if ( isarray )
			{
				Loop Parse, A_LoopField, `,, % ws "]"
					if ( A_LoopField != "" )
						obj[key++] := A_LoopField = quot ? literals.remove(1) : A_LoopField
			}
			; otherwise, parse the segment as key/value pairs
			else
			{
				Loop Parse, A_LoopField, `,
					Loop Parse, A_LoopField, :, % ws
						if ( A_Index = 1 )
							key := A_LoopField = quot ? literals.remove(1) : A_LoopField
						else if ( A_Index = 2 && A_LoopField != "" )
							obj[key] := A_LoopField = quot ? literals.remove(1) : A_LoopField
			}
			nest += A_Index > 1
		} ; Loop Parse, str, % "[{"

		If !--nest
			Break

		; Insert the newly closed object into the one on top of the stack, then pop the stack
		pbj := obj
		obj := objs.remove()
		obj[key := keys.remove()] := pbj
		If ( isarray := isarrays.remove() )
			key++

	} ; Loop Parse, str, % "]}"

	Return obj
} ; json_toobj( str )

json_fromobj( obj ) {

	If IsObject( obj )
	{
		isarray := 0 ; an empty object could be an array... but it ain't, says I
		for key in obj
			if ( key != ++isarray )
			{
				isarray := 0
				Break
			}

		for key, val in obj
			str .= ( A_Index = 1 ? "" : "," ) ( isarray ? "" : json_fromObj( key ) ":" ) json_fromObj( val )

		return isarray ? "[" str "]" : "{" str "}"
	}
	else if obj IS NUMBER
		return obj
;	else if obj IN null,true,false ; AutoHotkey does not natively distinguish these
;		return obj

	; Encode control characters, starting with backslash.
	StringReplace, obj, obj, \, \\, A
	StringReplace, obj, obj, % Chr(08), \b, A
	StringReplace, obj, obj, % A_Tab, \t, A
	StringReplace, obj, obj, `n, \n, A
	StringReplace, obj, obj, % Chr(12), \f, A
	StringReplace, obj, obj, `r, \r, A
	StringReplace, obj, obj, ", \", A
	StringReplace, obj, obj, /, \/, A
	While RegexMatch( obj, "[^\x20-\x7e]", key )
	{
		str := Asc( key )
		val := "\u" . Chr( ( ( str >> 12 ) & 15 ) + ( ( ( str >> 12 ) & 15 ) < 10 ? 48 : 55 ) )
				. Chr( ( ( str >> 8 ) & 15 ) + ( ( ( str >> 8 ) & 15 ) < 10 ? 48 : 55 ) )
				. Chr( ( ( str >> 4 ) & 15 ) + ( ( ( str >> 4 ) & 15 ) < 10 ? 48 : 55 ) )
				. Chr( ( str & 15 ) + ( ( str & 15 ) < 10 ? 48 : 55 ) )
		StringReplace, obj, obj, % key, % val, A
	}
	return """" obj """"
}

Code: Select all

<?php
//A PHP Script to receive JSON from AHK and send an email using mail()
//Change <[email protected]> on line 37 to the address you want the server to send from 


//this function outputs all our responses for external handling
function response($type, $data){
	echo json_encode(array("response" => $type, "message" => $data));exit();
}


//we allow some simple rest calls
if (isset($_SERVER['HTTP_ORIGIN'])) {
	header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
	header('Access-Control-Allow-Credentials: true');
	header('Access-Control-Max-Age: 86400');
}
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
	if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
		header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
	if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
		header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
	exit(0);
}


//handling log request
if (isset($_POST["obj"])){ 
	$log_data = $_POST["obj"];
	$log_data = str_replace("\":0000,","\":\"0000\",",$log_data);
	
	$email_array = json_decode($log_data, true);
	
	$to = $email_array['toEmail'];
	$subject = $email_array['emailSubject'];
	$txt = $email_array['emailMessage'];
	$headers = "From: " . $email_array['fromEmail'] . " <[email protected]>";
	
	mail($to,$subject,$txt,$headers);
		
	response($email_array, "Success");
}

?>

Return to “Scripts and Functions”

Who is online

Users browsing this forum: crocodile, DuyMinh and 19 guests