PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/services/Nntp/Services_Nntp_SpotPosting.php

http://github.com/spotweb/spotweb
PHP | 270 lines | 129 code | 51 blank | 90 comment | 10 complexity | 518fa39ea9ccdbb612445c3bc1cfda19 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, Apache-2.0, LGPL-3.0
  1. <?php
  2. class Services_Nntp_SpotPosting
  3. {
  4. private $_nntpEngine;
  5. private $_spotParseUtil;
  6. /*
  7. * constructor
  8. */
  9. public function __construct(Services_Nntp_Engine $nntpEngine)
  10. {
  11. $this->_spotParseUtil = new Services_Format_Util();
  12. $this->_nntpEngine = $nntpEngine;
  13. }
  14. // ctor
  15. /*
  16. * Post plain usenet message
  17. */
  18. private function postPlainMessage($newsgroup, $message, $additionalHeaders)
  19. {
  20. $header = 'Subject: '.utf8_decode($message['title'])."\r\n";
  21. $header .= 'Newsgroups: '.$newsgroup."\r\n";
  22. $header .= 'Message-ID: <'.$message['newmessageid'].">\r\n";
  23. $header .= 'X-Newsreader: SpotWeb v'.SPOTWEB_VERSION."\r\n";
  24. $header .= "X-No-Archive: yes\r\n";
  25. $header .= $additionalHeaders;
  26. return $this->_nntpEngine->post([$header, $message['body']]);
  27. }
  28. // postPlainMessage
  29. /*
  30. * Post a signed usenet message, we allow for additional headers
  31. * so this function can be used by anything
  32. */
  33. private function postSignedMessage($user, $serverPrivKey, $newsgroup, $message, $additionalHeaders)
  34. {
  35. // instantiate necessary objects
  36. $spotSigning = Services_Signing_Base::factory();
  37. // also by the SpotWeb server
  38. $server_signature = $spotSigning->signMessage($serverPrivKey, '<'.$message['newmessageid'].'>');
  39. $addHeaders = '';
  40. // Only add the user-signature header if there is none set yet
  41. if (stripos($additionalHeaders, 'X-User-Signature: ') === false) {
  42. // sign the messageid
  43. $user_signature = $spotSigning->signMessage($user['privatekey'], '<'.$message['newmessageid'].'>');
  44. $addHeaders .= 'X-User-Signature: '.$this->_spotParseUtil->spotPrepareBase64($user_signature['signature'])."\r\n";
  45. $addHeaders .= 'X-User-Key: '.$spotSigning->pubkeyToXml($user_signature['publickey'])."\r\n";
  46. } // if
  47. $addHeaders .= 'X-Server-Signature: '.$this->_spotParseUtil->spotPrepareBase64($server_signature['signature'])."\r\n";
  48. $addHeaders .= 'X-Server-Key: '.$spotSigning->pubkeyToXml($server_signature['publickey'])."\r\n";
  49. $addHeaders .= $additionalHeaders;
  50. return $this->postPlainMessage($newsgroup, $message, $addHeaders);
  51. }
  52. // postSignedMessage
  53. /*
  54. * Post a binary usenet message
  55. */
  56. public function postBinaryMessage($user, $newsgroup, $body, $additionalHeaders)
  57. {
  58. $chunkLen = (1024 * 1024);
  59. $segmentList = [];
  60. $spotSigning = Services_Signing_Base::factory();
  61. /*
  62. * Now start posting chunks of the binary files
  63. */
  64. while (strlen($body) > 0) {
  65. $message = [];
  66. /*
  67. * Cut of the first piece of the binary file, and remove it
  68. * from the source string
  69. */
  70. $chunk = substr($body, 0, $chunkLen - 1);
  71. $body = substr($body, $chunkLen - 1);
  72. /*
  73. * Split the body in parts of 900 characters
  74. */
  75. $message['body'] = $this->safe_chunk($this->_spotParseUtil->specialZipstr($chunk), 900);
  76. /*
  77. * Create an unique messageid and store it so we can return it
  78. * for the actual Spot creation
  79. */
  80. $message['newmessageid'] = $spotSigning->makeRandomStr(32).'@spot.net';
  81. $message['title'] = md5($message['body']);
  82. $addHeaders = 'From: '.$user['username'].' <'.trim($user['username']).'@spot.net>'."\r\n";
  83. $addHeaders .= 'Content-Type: text/plain; charset=ISO-8859-1'."\r\n";
  84. $addHeaders .= 'Content-Transfer-Encoding: 8bit'."\r\n";
  85. $addHeaders .= $additionalHeaders;
  86. /*
  87. * Actually post the image
  88. */
  89. $this->postPlainMessage($newsgroup, $message, $addHeaders);
  90. $segmentList[] = $message['newmessageid'];
  91. } // if
  92. return $segmentList;
  93. }
  94. // postBinaryMessage
  95. /*
  96. * Post a comment to a spot
  97. */
  98. public function postComment($user, $serverPrivKey, $newsgroup, $comment)
  99. {
  100. /*
  101. * Create the comment specific headers
  102. */
  103. $addHeaders = 'From: '.$user['username'].' <'.trim($user['username']).'@spot.net>'."\r\n";
  104. $addHeaders .= 'References: <'.$comment['inreplyto'].">\r\n";
  105. $addHeaders .= 'X-User-Rating: '.(int) $comment['rating']."\r\n";
  106. /*
  107. * And add the X-User-Avatar header if user has an avatar specified
  108. */
  109. if (!empty($user['avatar'])) {
  110. $tmpAvatar = explode("\r\n", $this->safe_chunk($user['avatar'], 900));
  111. foreach ($tmpAvatar as $avatarChunk) {
  112. if (strlen(trim($avatarChunk)) > 0) {
  113. $addHeaders .= 'X-User-Avatar: '.$avatarChunk."\r\n";
  114. } // if
  115. } // foreach
  116. } // if
  117. return $this->postSignedMessage($user, $serverPrivKey, $newsgroup, $comment, $addHeaders);
  118. }
  119. // postComment
  120. /*
  121. * Posts a spot file
  122. */
  123. public function postFullSpot($user, $serverPrivKey, $newsgroup, $spot)
  124. {
  125. // instantiate the necessary objects
  126. $spotSigning = Services_Signing_Base::factory();
  127. /*
  128. * Create the spotnet from header part accrdoing to the following structure:
  129. * From: [Nickname] <[PUBLICKEY-MODULO.USERSIGNATURE]@[CAT][KEY-ID][SUBCAT].[SIZE].[RANDOM].[DATE].[CUSTOM-ID].[CUSTOM-VALUE].[SIGNATURE]>
  130. */
  131. $spotHeader = ($spot['category'] + 1).$spot['key']; // Append the category and keyid
  132. // Process each subcategory and add them to the from header
  133. foreach ($spot['subcatlist'] as $subcat) {
  134. $spotHeader .= $subcat[0].str_pad(substr($subcat, 1), 2, '0', STR_PAD_LEFT);
  135. } // foreach
  136. $spotHeader .= '.'.$spot['filesize'];
  137. $spotHeader .= '.'. 10; // some kind of magic number?
  138. $spotHeader .= '.'.time();
  139. $spotHeader .= '.'.$spotSigning->makeRandomStr(4);
  140. $spotHeader .= '.'.$spotSigning->makeRandomStr(3);
  141. // If a tag is given, add it to the subject
  142. if (strlen(trim($spot['tag'])) > 0) {
  143. $spot['title'] = $spot['title'].' | '.$spot['tag'];
  144. } // if
  145. // Create the user-signature
  146. $user_signature = $spotSigning->signMessage($user['privatekey'], '<'.$spot['newmessageid'].'>');
  147. $header = 'X-User-Signature: '.$this->_spotParseUtil->spotPrepareBase64($user_signature['signature'])."\r\n";
  148. $header .= 'X-User-Key: '.$spotSigning->pubkeyToXml($user_signature['publickey'])."\r\n";
  149. // sign the header by using the users' key
  150. $header_signature = $spotSigning->signMessage($user['privatekey'], $spot['title'].$spotHeader.$spot['poster']);
  151. // sign the XML with the users' key
  152. $xml_signature = $spotSigning->signMessage($user['privatekey'], $spot['spotxml']);
  153. // Extract the users' publickey
  154. $userPubKey = $spotSigning->getPublicKey($user['privatekey']);
  155. // Create the From header
  156. $spotnetFrom = $user['username'].' <'.
  157. $this->_spotParseUtil->spotPrepareBase64($userPubKey['modulo']).
  158. '.'.
  159. $this->_spotParseUtil->spotPrepareBase64($user_signature['signature']).'@';
  160. $header = 'From: '.$spotnetFrom.$spotHeader.'.'.$this->_spotParseUtil->spotPrepareBase64($header_signature['signature']).">\r\n";
  161. // Add the Spotnet XML file, but split it in chunks of 900 characters
  162. $tmpXml = explode("\r\n", $this->safe_chunk($spot['spotxml'], 900));
  163. foreach ($tmpXml as $xmlChunk) {
  164. if (strlen(trim($xmlChunk)) > 0) {
  165. $header .= 'X-XML: '.$xmlChunk."\r\n";
  166. } // if
  167. } // foreach
  168. $header .= 'X-XML-Signature: '.$this->_spotParseUtil->spotPrepareBase64($xml_signature['signature'])."\r\n";
  169. // post the message
  170. return $this->postSignedMessage($user, $serverPrivKey, $newsgroup, $spot, $header);
  171. }
  172. // postFullSpot
  173. /*
  174. * Report a post as spam
  175. */
  176. public function reportSpotAsSpam($user, $serverPrivKey, $newsgroup, $report)
  177. {
  178. /*
  179. * Create the comment specific headers
  180. */
  181. $addHeaders = 'From: '.$user['username'].' <'.trim($user['username']).'@spot.net>'."\r\n";
  182. $addHeaders .= 'References: <'.$report['inreplyto'].">\r\n";
  183. return $this->postSignedMessage($user, $serverPrivKey, $newsgroup, $report, $addHeaders);
  184. }
  185. // reportSpotAsSpam
  186. /**
  187. * Function which mirrors chunk_split() of PHP, but tries to avoid
  188. * putting a whitespace character at the end of the line, because
  189. * some usenet servers discard that.
  190. *
  191. * @param $data
  192. * @param $maxLen
  193. * @param string $end
  194. *
  195. * @return string
  196. */
  197. private function safe_chunk($data, $maxLen, $end = "\r\n")
  198. {
  199. /*
  200. * We have to protect ourselves against having
  201. * only spaces in the stream, so we start with
  202. * the half of $maxLen, and work ourway up
  203. */
  204. $minLength = ceil($maxLen / 2);
  205. $totalChunk = '';
  206. while (strlen($data) > 0) {
  207. $sChunk = substr($data, 0, $minLength);
  208. $eChunk = substr($data, $minLength, $minLength);
  209. $eChunkLen = strlen($eChunk);
  210. while ((substr($eChunk, $eChunkLen - 1, 1) == ' ') && ($eChunkLen > 0)) {
  211. $eChunkLen--;
  212. } // while
  213. $totalChunk .= $sChunk.substr($eChunk, 0, $eChunkLen).$end;
  214. $data = substr($data, strlen($sChunk.substr($eChunk, 0, $eChunkLen)));
  215. } // while
  216. return $totalChunk;
  217. }
  218. // safe_chunk
  219. } // Services_Nntp_SpotPosting