function parseHeader($subj, $from, $date, $messageid, $rsaKeys) { # Initialize an empty array, we create a basic template in a few $spot = array(); /* * The "From" header is created using the following system: * * From: [Nickname] <[RANDOM or PUBLICKEY]@[CAT][KEY-ID][SUBCAT].[SIZE].[RANDOM].[DATE].[CUSTOM-ID].[CUSTOM-VALUE].[SIGNATURE]> * or * From: [Nickname] <[PUBLICKEY-MODULO.USERSIGNATURE]@[CAT][KEY-ID][SUBCAT].[SIZE].[RANDOM].[DATE].[CUSTOM-ID].[CUSTOM-VALUE].[SIGNATURE]> * * * First we want to extract everything after the @ but because a nickname could contain an @, we have to mangle it a bit */ $fromInfoPos = strpos($from, '<'); if ($fromInfoPos === false) { return false; } else { # Remove the posters' name and the <> characters $fromAddress = explode('@', substr($from, $fromInfoPos + 1, -1)); if (count($fromAddress) < 2) { return false; } # if $spot['header'] = $fromAddress[1]; /* * It is possible the part before the @ contains both the * users' signature as the spots signature as signed by the user */ $headerSignatureTemp = explode('.', $fromAddress[0]); $spot['selfsignedpubkey'] = $this->_util->spotUnprepareBase64($headerSignatureTemp[0]); if (isset($headerSignatureTemp[1])) { $spot['user-signature'] = $this->_util->spotUnprepareBase64($headerSignatureTemp[1]); } # if } # if /* * Initialize some basic variables. We set 'verified' to false so we can * exit this function at any time and the gathered data for this spot up til * then is stil ignored. */ $spot['verified'] = false; $spot['filesize'] = 0; $spot['messageid'] = $messageid; $spot['stamp'] = strtotime($date); /* * Split the .-delimited fields into an array so we can mangle it. We require * atleast six fields, if any less we can safely assume the spot is invalid */ $fields = explode('.', $spot['header']); if (count($fields) < 6) { return false; } # if /* * Extract the fixed fields from the header */ $spot['poster'] = substr($from, 0, $fromInfoPos - 1); $spot['category'] = substr($fields[0], 0, 1) - 1.0; $spot['keyid'] = (int) substr($fields[0], 1, 1); $spot['filesize'] = $fields[1]; $spot['subcata'] = ''; $spot['subcatb'] = ''; $spot['subcatc'] = ''; $spot['subcatd'] = ''; $spot['subcatz'] = ''; $spot['wassigned'] = false; $spot['spotterid'] = ''; $isRecentKey = $spot['keyid'] != 1; /* * If the keyid is invalid, abort trying to parse it */ if ($spot['keyid'] < 0) { return false; } # if /* * Listings of subcategories is dependent on the age of the spot. * * FTD spots just list all subcategories like: a9b4c0d5d15d11 * Newer spots always use three characters for each subcategory like: a09b04c00d05d15d11. * * We really do not care for this, we just parse them using the same code as the * first one. * * We pad $strCatList with an extra set of tokes so we always parse te last category, * we make sure any sanitycheck is passed by adding 3 tokens. */ $strCatList = strtolower(substr($fields[0], 2)) . '!!!'; $strCatListLen = strlen($strCatList); /* * Initialize some basic variables to use for sanitychecking (eg: valid subcats) */ $validSubcats = array('a' => true, 'b' => true, 'c' => true, 'd' => true, 'z' => true); $tmpCatBuild = ''; /* And just try to extract all given subcategories */ for ($i = 0; $i < $strCatListLen; $i++) { /* * If the current character is not an number, we found the next * subcategory. Add the current one to the list, and start * parsing the new one */ if (!is_numeric($strCatList[$i]) && !empty($tmpCatBuild)) { if (isset($validSubcats[$tmpCatBuild[0]])) { $spot['subcat' . $tmpCatBuild[0]] .= $tmpCatBuild[0] . (int) substr($tmpCatBuild, 1) . '|'; } # if $tmpCatBuild = ''; } # if $tmpCatBuild .= $strCatList[$i]; } # for /* * subcatz is a subcategory introduced in later Spotnet formats, we prefer to * always have this subcategory so we just fake it if it's not listed. */ if (empty($spot['subcatz'])) { $spot['subcatz'] = SpotCategories::createSubcatz($spot['category'], $spot['subcata'] . $spot['subcatb'] . $spot['subcatd']); } # if # map deprecated genre categories to their new genre category $spot['subcatd'] = SpotCategories::mapDeprecatedGenreSubCategories($spot['category'], $spot['subcatd'], $spot['subcatz']); $spot['subcatc'] = SpotCategories::mapLanguageSubCategories($spot['category'], $spot['subcatc'], $spot['subcatz']); if (strpos($subj, '=?') !== false && strpos($subj, '?=') !== false) { # This is an old format to parse, instantiate the legacy parsing $legacyParser = new Services_Format_ParsingLegacy(); # Make sure its as simple as possible $subj = str_replace('?= =?', '?==?', $subj); $subj = str_replace('\\r', '', trim($legacyParser->oldEncodingParse($subj))); $subj = str_replace('\\n', '', $subj); } # if if ($isRecentKey) { $tmp = explode('|', $subj); $spot['title'] = trim($tmp[0]); if (count($tmp) > 1) { $spot['tag'] = trim($tmp[1]); } else { $spot['tag'] = ''; } # else } else { $tmp = explode('|', $subj); if (count($tmp) <= 1) { $tmp = array($subj); } # if $spot['tag'] = trim($tmp[count($tmp) - 1]); # remove the tags from the array array_pop($tmp); array_pop($tmp); $spot['title'] = trim(implode('|', $tmp)); if (strpos($spot['title'], chr(0xc2)) !== false | strpos($spot['title'], chr(0xc3)) !== false) { # This is an old format to parse, instantiate the legacy parsing $legacyParser = new Services_Format_ParsingLegacy(); $spot['title'] = trim($legacyParser->oldEncodingParse($spot['title'])); } # if } # if recentKey # Title and poster fields are mandatory, we require it to validate the signature if (strlen($spot['title']) == 0 || strlen($spot['poster']) == 0) { return $spot; } # if /* * For any recentkey ( >1) or spots created after year-2010, we require the spot * to be signed */ $mustbeSigned = $isRecentKey | $spot['stamp'] > 1293870080; if ($mustbeSigned) { $spot['headersign'] = $fields[count($fields) - 1]; $spot['wassigned'] = strlen($spot['headersign']) != 0; } else { $spot['verified'] = true; $spot['wassigned'] = false; } # if doesnt need to be signed, pretend that it is /* * Don't verify spots which are already verified */ if ($spot['wassigned']) { /* * There are currently two known methods to which Spots are signed, * each having different charachteristics, making it a bit difficult * to work with this. * * The oldest method uses a secret private key and a signing server, we * name this method SPOTSIGN_V1. The users' public key is only available * in the XML header, not in the From header. This is the preferred method. * * The second method uses a so-called "self signed" spot (the spotter signs * the spots, posts the public key in the header and a hashcash is used to * prevent spamming). This method is called SPOTSIGN_V2. * */ if ($spot['keyid'] == 7) { /* * KeyID 7 has a special meaning, it defines a self-signed spot and * requires a hashcash */ $signingMethod = 2; } else { $signingMethod = 1; } # else switch ($signingMethod) { case 1: # the signature this header is signed with $signature = $this->_util->spotUnprepareBase64($spot['headersign']); /* * Make sure the key specified is an actual known key */ if (isset($rsaKeys[$spot['keyid']])) { if ($spot['keyid'] == 2 && ($spot['filesize'] = 999 && strlen($spot['selfsignedpubkey']) > 50)) { /* Check personal dispose message */ $signature = $this->_util->spotUnprepareBase64($spot['headersign']); $userSignedHash = sha1('<' . $spot['messageid'] . '>', false); $spot['verified'] = substr($userSignedHash, 0, 4) === '0000'; if ($spot['verified']) { $userRsaKey = array(2 => array('modulo' => $spot['selfsignedpubkey'], 'exponent' => 'AQAB')); if ($this->_spotSigning->verifySpotHeader($spot, $signature, $userRsaKey)) { $spot['spotterid'] = $this->_util->calculateSpotterId($spot['selfsignedpubkey']); } # if } # if } else { $spot['verified'] = $this->_spotSigning->verifySpotHeader($spot, $signature, $rsaKeys); } } # if break; # SPOTSIGN_V1 # SPOTSIGN_V1 case 2: # the signature this header is signed with $signature = $this->_util->spotUnprepareBase64($spot['headersign']); $userSignedHash = sha1('<' . $spot['messageid'] . '>', false); $spot['verified'] = substr($userSignedHash, 0, 4) === '0000'; /* * Create a fake RSA keyarray so we can validate it using our standard * infrastructure */ if ($spot['verified']) { $userRsaKey = array(7 => array('modulo' => $spot['selfsignedpubkey'], 'exponent' => 'AQAB')); /* * We cannot use this as a full measure to check the spot's validness yet, * because at least one Spotnet client feeds us invalid data for now */ if ($this->_spotSigning->verifySpotHeader($spot, $signature, $userRsaKey)) { /* * The users' public key (modulo) is posted in the header, lets * try this. */ $spot['spotterid'] = $this->_util->calculateSpotterId($spot['selfsignedpubkey']); } # if } # if break; # SPOTSIGN_V2 } # switch /* * Even more recent spots, contain the users' full publickey * in the header. This allows us to uniquely identify and verify * the poster of the spot. * * Try to extract this information. */ if ($spot['verified'] && !empty($spot['user-signature']) && !empty($spot['selfsignedpubkey'])) { /* * Extract the public key */ $spot['spotterid'] = $this->_util->calculateSpotterId($spot['selfsignedpubkey']); $spot['user-key'] = array('modulo' => $spot['selfsignedpubkey'], 'exponent' => 'AQAB'); /* * The spot contains the signature in the header of the spot */ $spot['verified'] = $this->_spotSigning->verifyFullSpot($spot); } # if } # if was signed /* * We convert the title and other fields to UTF8, we cannot * do this any earlier because it would break the RSA signature */ if ($spot !== false && $spot['verified']) { $spot['title'] = utf8_encode($spot['title']); $spot['poster'] = utf8_encode($spot['poster']); $spot['tag'] = utf8_encode($spot['tag']); # If a spot is in the future, fix it if (time() < $spot['stamp']) { $spot['stamp'] = time(); } # if } # if return $spot; }