diff options
Diffstat (limited to 'protocols/WhatsApp/src/WhatsAPI++')
| -rw-r--r-- | protocols/WhatsApp/src/WhatsAPI++/MediaUploader.cpp | 140 | ||||
| -rw-r--r-- | protocols/WhatsApp/src/WhatsAPI++/MediaUploader.h | 18 | ||||
| -rw-r--r-- | protocols/WhatsApp/src/WhatsAPI++/WAConnection.cpp | 159 | ||||
| -rw-r--r-- | protocols/WhatsApp/src/WhatsAPI++/WAConnection.h | 17 | 
4 files changed, 323 insertions, 11 deletions
diff --git a/protocols/WhatsApp/src/WhatsAPI++/MediaUploader.cpp b/protocols/WhatsApp/src/WhatsAPI++/MediaUploader.cpp new file mode 100644 index 0000000000..319ad02555 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/MediaUploader.cpp @@ -0,0 +1,140 @@ +#include "../common.h"
 +#include "MediaUploader.h"
 +
 +// TODO get rid of unneeded headers added by NETLIBHTTPREQUEST. it sould look like this:
 +//POST https://mmiXYZ.whatsapp.net/u/gOzeKj6U64LABC
 +//Content-Type: multipart/form-data; boundary=zzXXzzYYzzXXzzQQ
 +//Host: mmiXYZ.whatsapp.net
 +//User-Agent: WhatsApp/2.12.96 S40Version/14.26 Device/Nokia302
 +//Content-Length: 9999999999
 +//
 +//So remove these somehow:
 +//Accept-Encoding: deflate, gzip
 +//Connection: Keep-Alive
 +//Proxy-Connection: Keep-Alive
 +
 +static NETLIBHTTPHEADER s_imageHeaders[] =
 +{
 +	{ "User-Agent", ACCOUNT_USER_AGENT },
 +	{ "Content-Type", "multipart/form-data; boundary=zzXXzzYYzzXXzzQQ" }
 +};
 +
 +static std::vector<unsigned char>* sttFileToMem(const TCHAR *ptszFileName)
 +{
 +	HANDLE hFile = CreateFile(ptszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 +	if (hFile == INVALID_HANDLE_VALUE)
 +		return NULL;
 +
 +	DWORD upperSize, lowerSize = GetFileSize(hFile, &upperSize);
 +	std::vector<unsigned char> *result = new std::vector<unsigned char>(lowerSize);
 +	ReadFile(hFile, (void*)result->data(), lowerSize, &upperSize, NULL);
 +	CloseHandle(hFile);
 +	return result;
 +}
 +
 +namespace MediaUploader
 +{
 +	std::string sendData(std::string host, std::string head, std::string filePath, std::string tail)
 +	{
 +		// TODO string crap: can this be done more nicely?
 +		std::wstring stemp = std::wstring(filePath.begin(), filePath.end());
 +		LPCWSTR sw = stemp.c_str();
 +
 +		vector<unsigned char> *dataVector = sttFileToMem(sw);
 +
 +		vector<unsigned char> allVector(head.begin(), head.end());
 +		allVector.insert(allVector.end(), dataVector->begin(), dataVector->end());
 +		allVector.insert(allVector.end(), tail.begin(), tail.end());
 +
 +		NETLIBHTTPREQUEST nlhr = { sizeof(NETLIBHTTPREQUEST) };
 +		nlhr.requestType = REQUEST_POST;
 +		nlhr.szUrl = (char*)host.c_str();
 +		nlhr.headers = s_imageHeaders;
 +		nlhr.headersCount = _countof(s_imageHeaders);
 +		nlhr.flags = NLHRF_HTTP11 | NLHRF_GENERATEHOST | NLHRF_REMOVEHOST | NLHRF_SSL;
 +		nlhr.pData = (char*)allVector.data();
 +		nlhr.dataLength = (int)allVector.size();
 +
 +		NETLIBHTTPREQUEST* pnlhr = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION,
 +			(WPARAM)WASocketConnection::hNetlibUser, (LPARAM)&nlhr);
 +
 +		string data = pnlhr->pData;
 +
 +		if (!data.empty())
 +			return data;
 +		else return 0;
 +	}
 +
 +	std::string pushfile(std::string url, FMessage * message, std::string from)
 +	{
 +		return getPostString(url, message, from);
 +	}
 +
 +	std::string getPostString(std::string url, FMessage * message, std::string from)
 +	{
 +		string filePath = message->media_url;
 +		string to = message->key.remote_jid;
 +		long fileSize = message->media_size;
 +		string extension = split(filePath, '.')[1];
 +
 +		const BYTE *path = (const BYTE*)filePath.c_str();
 +
 +		uint8_t digest[16];
 +		md5_string(filePath, digest);
 +		char dest[33];
 +		bin2hex(digest, sizeof(digest), dest);
 +
 +		string cryptoname = dest;
 +		cryptoname += "." + extension;
 +		string boundary = "zzXXzzYYzzXXzzQQ";
 +
 +		string hBAOS = "--" + boundary + "\r\n";
 +		hBAOS += "Content-Disposition: form-data; name=\"to\"\r\n\r\n";
 +		hBAOS += to + "\r\n";
 +		hBAOS += "--" + boundary + "\r\n";
 +		hBAOS += "Content-Disposition: form-data; name=\"from\"\r\n\r\n";
 +		hBAOS += from + "\r\n";
 +		hBAOS += "--" + boundary + "\r\n";
 +		hBAOS += "Content-Disposition: form-data; name=\"file\"; filename=\"" + cryptoname + "\"\r\n";
 +		hBAOS += "Content-Type: " + getMimeFromExtension(extension) + "\r\n\r\n";
 +
 +		string fBAOS = "\r\n--" + boundary + "--\r\n";
 +		long contentlength = sizeof(hBAOS) + sizeof(fBAOS) + fileSize;
 +
 +		return sendData(url, hBAOS, filePath, fBAOS);
 +	}
 +
 +	static map<string, string> extensions;
 +
 +	std::string getMimeFromExtension(const string &extension)
 +	{
 +		if (extensions.empty()) {
 +			extensions["audio/3gpp"] = "3gp";
 +			extensions["audio/x-caf"] = "caf";
 +			extensions["audio/wav"] = "wav";
 +			extensions["audio/mpeg"] = "mp3";
 +			extensions["audio/mpeg3"] = "mp3";
 +			extensions["audio/x-mpeg-32"] = "mp3";
 +			extensions["audio/x-ms-wma"] = "wma";
 +			extensions["audio/ogg"] = "ogg";
 +			extensions["audio/aiff"] = "aif";
 +			extensions["audio/x-aiff"] = "aif";
 +			extensions["audio/mp4"] = "m4a";
 +			extensions["image/jpeg"] = "jpg";
 +			extensions["image/gif"] = "gif";
 +			extensions["image/png"] = "png";
 +			extensions["video/3gpp"] = "3gp";
 +			extensions["video/mp4"] = "mp4";
 +			extensions["video/quicktime"] = "mov";
 +			extensions["video/avi"] = "avi";
 +			extensions["video/msvideo"] = "avi";
 +			extensions["video/x-msvideo"] = "avi";
 +		}
 +
 +		for (auto it = extensions.begin(); it != extensions.end(); ++it)
 +			if ((*it).second == extension)
 +				return (*it).first;
 +
 +		return "";
 +	}
 +}
 diff --git a/protocols/WhatsApp/src/WhatsAPI++/MediaUploader.h b/protocols/WhatsApp/src/WhatsAPI++/MediaUploader.h new file mode 100644 index 0000000000..7fb6612095 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/MediaUploader.h @@ -0,0 +1,18 @@ +/*
 +* 
 +*/
 +#ifndef MEDIAUPLOADER_H_
 +#define MEDIAUPLOADER_H_
 +
 +using namespace std;
 +
 +namespace MediaUploader
 +{
 +	std::string pushfile(std::string url, FMessage * message, std::string from);
 +	std::string getPostString(std::string url, FMessage * message, std::string from);
 +	std::string sendData(std::string host, std::string head, std::string filePath, std::string tail);
 +	std::string getExtensionFromMime(string mime);
 +	std::string getMimeFromExtension(const string &extension);
 +};
 +
 +#endif /* MEDIAUPLOADER_H_ */
 diff --git a/protocols/WhatsApp/src/WhatsAPI++/WAConnection.cpp b/protocols/WhatsApp/src/WhatsAPI++/WAConnection.cpp index 113a69d2f1..c271a83be2 100644 --- a/protocols/WhatsApp/src/WhatsAPI++/WAConnection.cpp +++ b/protocols/WhatsApp/src/WhatsAPI++/WAConnection.cpp @@ -198,6 +198,9 @@ bool WAConnection::read() throw(WAException)  		parseReceipt(node);
  	else if (ProtocolTreeNode::tagEquals(node, "chatstate"))
  		parseChatStates(node);
 +	else {
 +		rawConn->log("Warning: Node parsing not handled:\n", tmp.c_str());
 +	}
  	delete node;
  	return true;
 @@ -546,15 +549,11 @@ void WAConnection::parseNotification(ProtocolTreeNode *node) throw(WAException)  		if (bodyNode != NULL && m_pGroupEventHandler != NULL)
  			m_pGroupEventHandler->onGroupNewSubject(from, participant, bodyNode->getDataAsString(), ts);
  	}
 +	else {
 +		rawConn->log("Warning: Unknown Notification received:\n", node->toString().c_str());
 +	}
 -	ProtocolTreeNode sendNode("ack");
 -	sendNode << XATTR("to", from) << XATTR("id", id) << XATTR("type", type) << XATTR("class", "notification");
 -	const string &to = node->getAttributeValue("to");
 -	if (!to.empty())
 -		sendNode << XATTR("from", to);
 -	if (!participant.empty())
 -		sendNode << XATTR("participant", participant);
 -	out.write(sendNode);
 +	sendAck(node, "notification");
  }
  void WAConnection::parsePresense(ProtocolTreeNode *node) throw(WAException)
 @@ -606,8 +605,7 @@ void WAConnection::parseReceipt(ProtocolTreeNode *node) throw(WAException)  		m_pEventHandler->onMessageStatusUpdate(msg);
  	}
 -	out.write(ProtocolTreeNode("ack")
 -		<< XATTR("to", from) << XATTR("id", id) << XATTR("type", "read") << XATTRI("t", time(0)));
 +	sendAck(node,"receipt");
  }
  std::vector<ProtocolTreeNode*>* WAConnection::processGroupSettings(const std::vector<GroupSetting>& groups)
 @@ -730,6 +728,26 @@ void WAConnection::sendInactive() throw(WAException)  	out.write(ProtocolTreeNode("presence") << XATTR("type", "inactive"));
  }
 +void WAConnection::sendAck(ProtocolTreeNode *node, const char *classType)
 +{
 +	const string &from = node->getAttributeValue("from");
 +	const string &to = node->getAttributeValue("to");
 +	const string &participant = node->getAttributeValue("participant");
 +	const string &id = node->getAttributeValue("id");
 +	const string &type = node->getAttributeValue("type");
 +
 +	ProtocolTreeNode sendNode("ack");
 +	sendNode << XATTR("to", from) << XATTR("id", id) << XATTR("class", classType);
 +	if (!to.empty())
 +		sendNode << XATTR("from", to);
 +	if (!participant.empty())
 +		sendNode << XATTR("participant", participant);
 +	if (!type.empty())
 +		sendNode << XATTR("type", type);
 +
 +	out.write(sendNode);
 +}
 +
  /////////////////////////////////////////////////////////////////////////////////////////
  ProtocolTreeNode* WAConnection::getMessageNode(FMessage* message, ProtocolTreeNode *child)
 @@ -757,6 +775,21 @@ void WAConnection::sendMessageWithMedia(FMessage* message)  throw (WAException)  	if (message->media_wa_type == FMessage::WA_TYPE_SYSTEM)
  		throw new WAException("Cannot send system message over the network");
 +	// request node for image, audio or video upload
 +	if (message->media_wa_type == FMessage::WA_TYPE_IMAGE || message->media_wa_type == FMessage::WA_TYPE_AUDIO || message->media_wa_type == FMessage::WA_TYPE_VIDEO) {
 +		std::string id = makeId("iq_");
 +		this->pending_server_requests[id] = new MediaUploadResponseHandler(this, *message);
 +		
 +		ProtocolTreeNode *mediaNode = new ProtocolTreeNode("media");
 +		mediaNode << XATTR("hash", message->media_name) << XATTR("type", FMessage::getMessage_WA_Type_StrValue(message->media_wa_type)) << XATTR("size", std::to_string(message->media_size));
 +	
 +		ProtocolTreeNode *n = new ProtocolTreeNode("iq", mediaNode);
 +		n << XATTR("id", id) << XATTR("to", this->domain) << XATTR("type", "set") << XATTR("xmlns", "w:m");
 +		out.write(*n);
 +		delete n;
 +		return;
 +	}
 +
  	ProtocolTreeNode *mediaNode;
  	if (message->media_wa_type == FMessage::WA_TYPE_CONTACT && !message->media_name.empty()) {
  		ProtocolTreeNode *vcardNode = new ProtocolTreeNode("vcard", new std::vector<unsigned char>(message->data.begin(), message->data.end()))
 @@ -781,6 +814,110 @@ void WAConnection::sendMessageWithMedia(FMessage* message)  throw (WAException)  	ProtocolTreeNode *n = WAConnection::getMessageNode(message, mediaNode);
  	out.write(*n);
  	delete n;
 +
 +}
 +
 +// TODO remove this code from WA purple
 +static std::string query_field(std::string work, std::string lo, bool integer = false)
 +{
 +	size_t p = work.find("\"" + lo + "\"");
 +	if (p == std::string::npos)
 +		return "";
 +
 +	work = work.substr(p + ("\"" + lo + "\"").size());
 +
 +	p = work.find("\"");
 +	if (integer)
 +		p = work.find(":");
 +	if (p == std::string::npos)
 +		return "";
 +
 +	work = work.substr(p + 1);
 +
 +	p = 0;
 +	while (p < work.size()) {
 +		if (work[p] == '"' && (p == 0 || work[p - 1] != '\\'))
 +			break;
 +		p++;
 +	}
 +	if (integer) {
 +		p = 0;
 +		while (p < work.size() && work[p] >= '0' && work[p] <= '9')
 +			p++;
 +	}
 +	if (p == std::string::npos)
 +		return "";
 +
 +	work = work.substr(0, p);
 +
 +	return work;
 +}
 +
 +void WAConnection::processUploadResponse(ProtocolTreeNode * node, FMessage * message)
 +{
 +	ProtocolTreeNode* duplicate = node->getChild("duplicate");
 +
 +	// setup vars for media message
 +	string fileType;
 +	string caption, url, fileName, fileSize, filePath, fileHash;
 +	caption = message->data;
 +
 +	// parse node
 +	if (duplicate != NULL) {
 +		url = duplicate->getAttributeValue("url");
 +		fileSize = duplicate->getAttributeValue("size");
 +		fileHash = duplicate->getAttributeValue("filehash");
 +		fileType = duplicate->getAttributeValue("type");
 +		string tempfileName = duplicate->getAttributeValue("url");
 +		size_t index = tempfileName.find_last_of('/')+1;
 +		fileName = tempfileName.substr(index);
 +	}
 +	else {
 +		string json = MediaUploader::pushfile(node->getChild("media")->getAttributeValue("url"),message, this->user);
 +		if (json.empty())
 +			return;
 +
 +		//TODO why does the JSONNode not work? -> Throws some exception when trying to access elements after parsing.
 +
 +		/*JSONNode resp = JSONNode::parse(json.c_str());
 +		fileName = resp["name"].as_string();
 +		url = resp["url"].as_string();
 +		fileSize = resp["size"].as_string();
 +		fileHash = resp["filehash"].as_string();
 +		fileType = resp["type"].as_string();
 +		*/
 +
 +		// TODO remove this code from WA purple
 +		size_t offset = json.find("{");
 +		if (offset == std::string::npos)
 +			return;
 +		json = json.substr(offset + 1);
 +
 +		/* Look for closure */
 +		size_t cl = json.find("{");
 +		if (cl == std::string::npos)
 +			cl = json.size();
 +		std::string work = json.substr(0, cl);
 +
 +		fileName = query_field(work, "name");
 +		url = query_field(work, "url");
 +		fileSize = query_field(work, "size");
 +		fileHash = query_field(work, "filehash");
 +		fileType = query_field(work, "type");
 +
 +	}
 +
 +	// TODO show caption and(?) link to media file in message window and history
 +	ProtocolTreeNode *mediaNode = new ProtocolTreeNode("media");
 +	mediaNode << XATTR("type", fileType)
 +		<< XATTR("url", url) << XATTR("encoding", "raw") << XATTR("file", fileName) 
 +		<< XATTR("size", fileSize)<< XATTR("caption", caption);
 +
 +	ProtocolTreeNode * messageNode = new ProtocolTreeNode("message", mediaNode);
 +	messageNode << XATTR("to", message->key.remote_jid) << XATTR("type", "media")
 +		<< XATTR("id", message->key.id) << XATTRI("t", (int)time(0));
 +	out.write(*messageNode);
 +	delete messageNode;
  }
  void WAConnection::sendMessageWithBody(FMessage* message) throw (WAException)
 @@ -794,7 +931,7 @@ void WAConnection::sendMessageWithBody(FMessage* message) throw (WAException)  void WAConnection::sendMessageReceived(const FMessage &message) throw(WAException)
  {
  	out.write(ProtocolTreeNode("receipt") << XATTR("type", "read")
 -		<< XATTR("to", message.key.remote_jid) << XATTR("id", message.key.id) << XATTRI("t", (int)time(0)));
 +		<< XATTR("to", message.key.remote_jid) << XATTR("id", message.key.id));
  }
  /////////////////////////////////////////////////////////////////////////////////////////
 diff --git a/protocols/WhatsApp/src/WhatsAPI++/WAConnection.h b/protocols/WhatsApp/src/WhatsAPI++/WAConnection.h index 03637b8c3a..a1a366c13e 100644 --- a/protocols/WhatsApp/src/WhatsAPI++/WAConnection.h +++ b/protocols/WhatsApp/src/WhatsAPI++/WAConnection.h @@ -20,6 +20,7 @@  #include "utilities.h"
  #include "BinTreeNodeReader.h"
  #include "BinTreeNodeWriter.h"
 +#include "MediaUploader.h"
  #pragma warning(disable : 4290)
 @@ -314,6 +315,19 @@ class WAConnection  		}
  	};
 +	class MediaUploadResponseHandler : public IqResultHandler {
 +	private:
 +		FMessage message;
 +	public:
 +		MediaUploadResponseHandler(WAConnection* con, const FMessage &message) :IqResultHandler(con) { this->message = message; }
 +		virtual void parse(ProtocolTreeNode* node, const std::string &from) throw (WAException) {
 +			this->con->processUploadResponse(node, &message);
 +		}
 +		void error(ProtocolTreeNode* node) throw (WAException) {
 +			con->logData("Error on Media Upload Request: %s", node->toString().c_str());
 +		}
 +	};
 +
  	friend class WALogin;
  private:
 @@ -335,6 +349,8 @@ private:  	void parseReceipt(ProtocolTreeNode *node) throw (WAException);
  	std::map<string, string> parseCategories(ProtocolTreeNode* node) throw(WAException);
 +	void processUploadResponse(ProtocolTreeNode *node, FMessage *message);
 +
  	void sendMessageWithMedia(FMessage* message) throw(WAException);
  	void sendMessageWithBody(FMessage* message) throw(WAException);
  	ProtocolTreeNode* getReceiptAck(const std::string &to, const std::string &id, const std::string &receiptType) throw(WAException);
 @@ -381,6 +397,7 @@ public:  	bool read() throw(WAException);
 +	void sendAck(ProtocolTreeNode * node, const char *classType);
  	void sendPing() throw(WAException);
  	void sendQueryLastOnline(const std::string &jid) throw (WAException);
  	void sendPong(const std::string &id) throw(WAException);
  | 
