michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsIOService.h" michael@0: #include "nsBinHexDecoder.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIStreamConverterService.h" michael@0: #include "nsCRT.h" michael@0: #include "nsIPipe.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "netCore.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "prnetdb.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIURL.h" michael@0: michael@0: #include "nsIMIMEService.h" michael@0: #include "nsMimeTypes.h" michael@0: #include michael@0: michael@0: nsBinHexDecoder::nsBinHexDecoder() : michael@0: mState(0), mCRC(0), mFileCRC(0), mOctetin(26), michael@0: mDonePos(3), mInCRC(0), mCount(0), mMarker(0), mPosInbuff(0), michael@0: mPosOutputBuff(0) michael@0: { michael@0: mDataBuffer = nullptr; michael@0: mOutgoingBuffer = nullptr; michael@0: michael@0: mOctetBuf.val = 0; michael@0: mHeader.type = 0; michael@0: mHeader.creator = 0; michael@0: mHeader.flags = 0; michael@0: mHeader.dlen = 0; michael@0: mHeader.rlen = 0; michael@0: } michael@0: michael@0: nsBinHexDecoder::~nsBinHexDecoder() michael@0: { michael@0: if (mDataBuffer) michael@0: nsMemory::Free(mDataBuffer); michael@0: if (mOutgoingBuffer) michael@0: nsMemory::Free(mOutgoingBuffer); michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(nsBinHexDecoder) michael@0: NS_IMPL_RELEASE(nsBinHexDecoder) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsBinHexDecoder) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStreamConverter) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStreamListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: michael@0: // The binhex 4.0 decoder table.... michael@0: michael@0: static const signed char binhex_decode[256] = michael@0: { michael@0: -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, michael@0: -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, michael@0: -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, -1, -1, michael@0: 13, 14, 15, 16, 17, 18, 19, -1, 20, 21, -1, -1, -1, -1, -1, -1, michael@0: 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, michael@0: 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, 47, -1, -1, -1, -1, michael@0: 48, 49, 50, 51, 52, 53, 54, -1, 55, 56, 57, 58, 59, 60, -1, -1, michael@0: 61, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, michael@0: -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, michael@0: -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, michael@0: -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, michael@0: -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, michael@0: -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, michael@0: -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, michael@0: -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, michael@0: -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, michael@0: }; michael@0: michael@0: #define BHEXVAL(c) (binhex_decode[(unsigned char) c]) michael@0: michael@0: ////////////////////////////////////////////////////// michael@0: // nsIStreamConverter methods... michael@0: ////////////////////////////////////////////////////// michael@0: michael@0: NS_IMETHODIMP michael@0: nsBinHexDecoder::Convert(nsIInputStream *aFromStream, michael@0: const char *aFromType, michael@0: const char *aToType, michael@0: nsISupports *aCtxt, michael@0: nsIInputStream **aResultStream) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsBinHexDecoder::AsyncConvertData(const char *aFromType, michael@0: const char *aToType, michael@0: nsIStreamListener *aListener, michael@0: nsISupports *aCtxt) michael@0: { michael@0: NS_ASSERTION(aListener && aFromType && aToType, michael@0: "null pointer passed into bin hex converter"); michael@0: michael@0: // hook up our final listener. this guy gets the various On*() calls we want to throw michael@0: // at him. michael@0: // michael@0: mNextListener = aListener; michael@0: return (aListener) ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////// michael@0: // nsIStreamListener methods... michael@0: ////////////////////////////////////////////////////// michael@0: NS_IMETHODIMP michael@0: nsBinHexDecoder::OnDataAvailable(nsIRequest* request, michael@0: nsISupports *aCtxt, michael@0: nsIInputStream *aStream, michael@0: uint64_t aSourceOffset, michael@0: uint32_t aCount) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (mOutputStream && mDataBuffer && aCount > 0) michael@0: { michael@0: uint32_t numBytesRead = 0; michael@0: while (aCount > 0) // while we still have bytes to copy... michael@0: { michael@0: aStream->Read(mDataBuffer, std::min(aCount, nsIOService::gDefaultSegmentSize - 1), &numBytesRead); michael@0: if (aCount >= numBytesRead) michael@0: aCount -= numBytesRead; // subtract off the number of bytes we just read michael@0: else michael@0: aCount = 0; michael@0: michael@0: // Process this new chunk of bin hex data... michael@0: ProcessNextChunk(request, aCtxt, numBytesRead); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsBinHexDecoder::ProcessNextState(nsIRequest * aRequest, nsISupports * aContext) michael@0: { michael@0: nsresult status = NS_OK; michael@0: uint16_t tmpcrc, cval; michael@0: unsigned char ctmp, c = mRlebuf; michael@0: michael@0: /* do CRC */ michael@0: ctmp = mInCRC ? c : 0; michael@0: cval = mCRC & 0xf000; michael@0: tmpcrc = ((uint16_t) (mCRC << 4) | (ctmp >> 4)) ^ (cval | (cval >> 7) | (cval >> 12)); michael@0: cval = tmpcrc & 0xf000; michael@0: mCRC = ((uint16_t) (tmpcrc << 4) | (ctmp & 0x0f)) ^ (cval | (cval >> 7) | (cval >> 12)); michael@0: michael@0: /* handle state */ michael@0: switch (mState) michael@0: { michael@0: case BINHEX_STATE_START: michael@0: mState = BINHEX_STATE_FNAME; michael@0: mCount = 0; michael@0: michael@0: // c & 63 returns the length of mName. So if we need the length, that's how michael@0: // you can figure it out.... michael@0: mName.SetLength(c & 63); michael@0: if (mName.Length() != (c & 63)) { michael@0: /* XXX ProcessNextState/ProcessNextChunk aren't rv checked */ michael@0: mState = BINHEX_STATE_DONE; michael@0: } michael@0: break; michael@0: michael@0: case BINHEX_STATE_FNAME: michael@0: mName.BeginWriting()[mCount] = c; michael@0: michael@0: if (++mCount > mName.Length()) michael@0: { michael@0: // okay we've figured out the file name....set the content type on the channel michael@0: // based on the file name AND issue our delayed on start request.... michael@0: michael@0: DetectContentType(aRequest, mName); michael@0: // now propagate the on start request michael@0: mNextListener->OnStartRequest(aRequest, aContext); michael@0: michael@0: mState = BINHEX_STATE_HEADER; michael@0: mCount = 0; michael@0: } michael@0: break; michael@0: michael@0: case BINHEX_STATE_HEADER: michael@0: ((char *) &mHeader)[mCount] = c; michael@0: if (++mCount == 18) michael@0: { michael@0: if (sizeof(binhex_header) != 18) /* fix an alignment problem in some OSes */ michael@0: { michael@0: char *p = (char *)&mHeader; michael@0: p += 19; michael@0: for (c = 0; c < 8; c++) michael@0: { michael@0: *p = *(p-2); michael@0: --p; michael@0: } michael@0: } michael@0: michael@0: mState = BINHEX_STATE_HCRC; michael@0: mInCRC = 1; michael@0: mCount = 0; michael@0: } michael@0: break; michael@0: michael@0: case BINHEX_STATE_DFORK: michael@0: case BINHEX_STATE_RFORK: michael@0: mOutgoingBuffer[mPosOutputBuff++] = c; michael@0: if (--mCount == 0) michael@0: { michael@0: /* only output data fork in the non-mac system. */ michael@0: if (mState == BINHEX_STATE_DFORK) michael@0: { michael@0: uint32_t numBytesWritten = 0; michael@0: mOutputStream->Write(mOutgoingBuffer, mPosOutputBuff, &numBytesWritten); michael@0: if (int32_t(numBytesWritten) != mPosOutputBuff) michael@0: status = NS_ERROR_FAILURE; michael@0: michael@0: // now propagate the data we just wrote michael@0: mNextListener->OnDataAvailable(aRequest, aContext, mInputStream, 0, numBytesWritten); michael@0: } michael@0: else michael@0: status = NS_OK; /* do nothing for resource fork. */ michael@0: michael@0: mPosOutputBuff = 0; michael@0: michael@0: if (status != NS_OK) michael@0: mState = BINHEX_STATE_DONE; michael@0: else michael@0: ++mState; michael@0: michael@0: mInCRC = 1; michael@0: } michael@0: else if (mPosOutputBuff >= (int32_t) nsIOService::gDefaultSegmentSize) michael@0: { michael@0: if (mState == BINHEX_STATE_DFORK) michael@0: { michael@0: uint32_t numBytesWritten = 0; michael@0: mOutputStream->Write(mOutgoingBuffer, mPosOutputBuff, &numBytesWritten); michael@0: if (int32_t(numBytesWritten) != mPosOutputBuff) michael@0: status = NS_ERROR_FAILURE; michael@0: michael@0: mNextListener->OnDataAvailable(aRequest, aContext, mInputStream, 0, numBytesWritten); michael@0: mPosOutputBuff = 0; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case BINHEX_STATE_HCRC: michael@0: case BINHEX_STATE_DCRC: michael@0: case BINHEX_STATE_RCRC: michael@0: if (!mCount++) michael@0: mFileCRC = (unsigned short) c << 8; michael@0: else michael@0: { michael@0: if ((mFileCRC | c) != mCRC) michael@0: { michael@0: mState = BINHEX_STATE_DONE; michael@0: break; michael@0: } michael@0: michael@0: /* passed the CRC check!!!*/ michael@0: mCRC = 0; michael@0: if (++mState == BINHEX_STATE_FINISH) michael@0: { michael@0: // when we reach the finished state...fire an on stop request on the event listener... michael@0: mNextListener->OnStopRequest(aRequest, aContext, NS_OK); michael@0: mNextListener = 0; michael@0: michael@0: /* now We are done with everything. */ michael@0: ++mState; michael@0: break; michael@0: } michael@0: michael@0: if (mState == BINHEX_STATE_DFORK) michael@0: mCount = PR_ntohl(mHeader.dlen); michael@0: else michael@0: { michael@0: // we aren't processing the resurce Fork. uncomment this line if we make this converter michael@0: // smart enough to do this in the future. michael@0: // mCount = PR_ntohl(mHeader.rlen); /* it should in host byte order */ michael@0: mCount = 0; michael@0: } michael@0: michael@0: if (mCount) { michael@0: mInCRC = 0; michael@0: } else { michael@0: /* nothing inside, so skip to the next state. */ michael@0: ++mState; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsBinHexDecoder::ProcessNextChunk(nsIRequest * aRequest, nsISupports * aContext, uint32_t numBytesInBuffer) michael@0: { michael@0: bool foundStart; michael@0: int16_t octetpos, c = 0; michael@0: uint32_t val; michael@0: mPosInDataBuffer = 0; // use member variable. michael@0: michael@0: NS_ENSURE_TRUE(numBytesInBuffer > 0, NS_ERROR_FAILURE); michael@0: michael@0: // if it is the first time, seek to the right start place. michael@0: if (mState == BINHEX_STATE_START) michael@0: { michael@0: foundStart = false; michael@0: // go through the line, until we get a ':' michael@0: while (mPosInDataBuffer < numBytesInBuffer) michael@0: { michael@0: c = mDataBuffer[mPosInDataBuffer++]; michael@0: while (c == nsCRT::CR || c == nsCRT::LF) michael@0: { michael@0: if (mPosInDataBuffer >= numBytesInBuffer) michael@0: break; michael@0: michael@0: c = mDataBuffer[mPosInDataBuffer++]; michael@0: if (c == ':') michael@0: { michael@0: foundStart = true; michael@0: break; michael@0: } michael@0: } michael@0: if (foundStart) break; /* we got the start point. */ michael@0: } michael@0: michael@0: if (mPosInDataBuffer >= numBytesInBuffer) michael@0: return NS_OK; /* we meet buff end before we get the start point, wait till next fills. */ michael@0: michael@0: if (c != ':') michael@0: return NS_ERROR_FAILURE; /* can't find the start character. */ michael@0: } michael@0: michael@0: while (mState != BINHEX_STATE_DONE) michael@0: { michael@0: /* fill in octetbuf */ michael@0: do michael@0: { michael@0: if (mPosInDataBuffer >= numBytesInBuffer) michael@0: return NS_OK; /* end of buff, go on for the nxet calls. */ michael@0: michael@0: c = GetNextChar(numBytesInBuffer); michael@0: if (c == 0) return NS_OK; michael@0: michael@0: if ((val = BHEXVAL(c)) == uint32_t(-1)) michael@0: { michael@0: /* we incount an invalid character. */ michael@0: if (c) michael@0: { michael@0: /* rolling back. */ michael@0: --mDonePos; michael@0: if (mOctetin >= 14) michael@0: --mDonePos; michael@0: if (mOctetin >= 20) michael@0: --mDonePos; michael@0: } michael@0: break; michael@0: } michael@0: mOctetBuf.val |= val << mOctetin; michael@0: } michael@0: while ((mOctetin -= 6) > 2); michael@0: michael@0: /* handle decoded characters -- run length encoding (rle) detection */ michael@0: michael@0: // We put decoded chars into mOctetBuf.val in order from high to low (via michael@0: // bitshifting, above). But we want to byte-address them, so we want the michael@0: // first byte to correspond to the high byte. In other words, we want michael@0: // these bytes to be in network order. michael@0: mOctetBuf.val = PR_htonl(mOctetBuf.val); michael@0: michael@0: for (octetpos = 0; octetpos < mDonePos; ++octetpos) michael@0: { michael@0: c = mOctetBuf.c[octetpos]; michael@0: michael@0: if (c == 0x90 && !mMarker++) michael@0: continue; michael@0: michael@0: if (mMarker) michael@0: { michael@0: if (c == 0) michael@0: { michael@0: mRlebuf = 0x90; michael@0: ProcessNextState(aRequest, aContext); michael@0: } michael@0: else michael@0: { michael@0: /* we are in the run length mode */ michael@0: while (--c > 0) michael@0: ProcessNextState(aRequest, aContext); michael@0: } michael@0: mMarker = 0; michael@0: } michael@0: else michael@0: { michael@0: mRlebuf = (unsigned char) c; michael@0: ProcessNextState(aRequest, aContext); michael@0: } michael@0: michael@0: if (mState >= BINHEX_STATE_DONE) michael@0: break; michael@0: } michael@0: michael@0: /* prepare for next 3 characters. */ michael@0: if (mDonePos < 3 && mState < BINHEX_STATE_DONE) michael@0: mState = BINHEX_STATE_DONE; michael@0: michael@0: mOctetin = 26; michael@0: mOctetBuf.val = 0; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: int16_t nsBinHexDecoder::GetNextChar(uint32_t numBytesInBuffer) michael@0: { michael@0: char c = 0; michael@0: michael@0: while (mPosInDataBuffer < numBytesInBuffer) michael@0: { michael@0: c = mDataBuffer[mPosInDataBuffer++]; michael@0: if (c != nsCRT::LF && c != nsCRT::CR) michael@0: break; michael@0: } michael@0: return (c == nsCRT::LF || c == nsCRT::CR) ? 0 : (int) c; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////// michael@0: // nsIRequestObserver methods... michael@0: ////////////////////////////////////////////////////// michael@0: michael@0: NS_IMETHODIMP michael@0: nsBinHexDecoder::OnStartRequest(nsIRequest* request, nsISupports *aCtxt) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: NS_ENSURE_TRUE(mNextListener, NS_ERROR_FAILURE); michael@0: michael@0: mDataBuffer = (char *) moz_malloc((sizeof(char) * nsIOService::gDefaultSegmentSize)); michael@0: mOutgoingBuffer = (char *) moz_malloc((sizeof(char) * nsIOService::gDefaultSegmentSize)); michael@0: if (!mDataBuffer || !mOutgoingBuffer) return NS_ERROR_FAILURE; // out of memory; michael@0: michael@0: // now we want to create a pipe which we'll use to write our converted data... michael@0: rv = NS_NewPipe(getter_AddRefs(mInputStream), getter_AddRefs(mOutputStream), michael@0: nsIOService::gDefaultSegmentSize, michael@0: nsIOService::gDefaultSegmentSize, michael@0: true, true); michael@0: michael@0: // don't propagate the on start request to mNextListener until we have determined the content type. michael@0: return rv; michael@0: } michael@0: michael@0: // Given the fileName we discovered inside the bin hex decoding, figure out the michael@0: // content type and set it on the channel associated with the request. If the michael@0: // filename tells us nothing useful, just report an unknown type and let the michael@0: // unknown decoder handle things. michael@0: nsresult nsBinHexDecoder::DetectContentType(nsIRequest* aRequest, michael@0: const nsAFlatCString &aFilename) michael@0: { michael@0: if (aFilename.IsEmpty()) { michael@0: // Nothing to do here. michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr channel(do_QueryInterface(aRequest, &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr mimeService(do_GetService("@mozilla.org/mime;1", &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString contentType; michael@0: michael@0: // extract the extension from aFilename and look it up. michael@0: const char * fileExt = strrchr(aFilename.get(), '.'); michael@0: if (!fileExt) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mimeService->GetTypeFromExtension(nsDependentCString(fileExt), contentType); michael@0: michael@0: // Only set the type if it's not empty and, to prevent recursive loops, not the binhex type michael@0: if (!contentType.IsEmpty() && !contentType.Equals(APPLICATION_BINHEX)) { michael@0: channel->SetContentType(contentType); michael@0: } else { michael@0: channel->SetContentType(NS_LITERAL_CSTRING(UNKNOWN_CONTENT_TYPE)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsBinHexDecoder::OnStopRequest(nsIRequest* request, nsISupports *aCtxt, michael@0: nsresult aStatus) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mNextListener) return NS_ERROR_FAILURE; michael@0: // don't do anything here...we'll fire our own on stop request when we are done michael@0: // processing the data.... michael@0: michael@0: return rv; michael@0: }