michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 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 "nsTXTToHTMLConv.h" michael@0: #include "nsEscape.h" michael@0: #include "nsStringStream.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsIChannel.h" michael@0: #include michael@0: michael@0: #define TOKEN_DELIMITERS MOZ_UTF16("\t\r\n ") michael@0: michael@0: // nsISupports methods michael@0: NS_IMPL_ISUPPORTS(nsTXTToHTMLConv, michael@0: nsIStreamConverter, michael@0: nsITXTToHTMLConv, michael@0: nsIRequestObserver, michael@0: nsIStreamListener) michael@0: michael@0: michael@0: // nsIStreamConverter methods michael@0: NS_IMETHODIMP michael@0: nsTXTToHTMLConv::Convert(nsIInputStream *aFromStream, michael@0: const char *aFromType, const char *aToType, michael@0: nsISupports *aCtxt, nsIInputStream * *_retval) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTXTToHTMLConv::AsyncConvertData(const char *aFromType, michael@0: const char *aToType, michael@0: nsIStreamListener *aListener, michael@0: nsISupports *aCtxt) michael@0: { michael@0: NS_ASSERTION(aListener, "null pointer"); michael@0: mListener = aListener; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // nsIRequestObserver methods michael@0: NS_IMETHODIMP michael@0: nsTXTToHTMLConv::OnStartRequest(nsIRequest* request, nsISupports *aContext) michael@0: { michael@0: mBuffer.AssignLiteral("\n"); michael@0: mBuffer.Append(mPageTitle); michael@0: mBuffer.AppendLiteral("\n\n"); michael@0: if (mPreFormatHTML) { // Use
 tags
michael@0:         mBuffer.AppendLiteral("
\n");
michael@0:     }
michael@0: 
michael@0:     // Push mBuffer to the listener now, so the initial HTML will not
michael@0:     // be parsed in OnDataAvailable().
michael@0: 
michael@0:     nsCOMPtr channel = do_QueryInterface(request);
michael@0:     if (channel)
michael@0:         channel->SetContentType(NS_LITERAL_CSTRING("text/html"));
michael@0:     // else, assume there is a channel somewhere that knows what it is doing!
michael@0: 
michael@0:     nsresult rv = mListener->OnStartRequest(request, aContext);
michael@0:     if (NS_FAILED(rv)) return rv;
michael@0: 
michael@0:     // The request may have been canceled, and if that happens, we want to
michael@0:     // suppress calls to OnDataAvailable.
michael@0:     request->GetStatus(&rv);
michael@0:     if (NS_FAILED(rv)) return rv;
michael@0: 
michael@0:     nsCOMPtr inputData;
michael@0:     rv = NS_NewStringInputStream(getter_AddRefs(inputData), mBuffer);
michael@0:     if (NS_FAILED(rv)) return rv;
michael@0: 
michael@0:     rv = mListener->OnDataAvailable(request, aContext,
michael@0:                                     inputData, 0, mBuffer.Length());
michael@0:     if (NS_FAILED(rv)) return rv;
michael@0:     mBuffer.Truncate();
michael@0:     return rv;
michael@0: }
michael@0: 
michael@0: NS_IMETHODIMP
michael@0: nsTXTToHTMLConv::OnStopRequest(nsIRequest* request, nsISupports *aContext,
michael@0:                                nsresult aStatus)
michael@0: {
michael@0:     nsresult rv = NS_OK;
michael@0:     if (mToken) {
michael@0:         // we still have an outstanding token
michael@0:         NS_ASSERTION(mToken->prepend,
michael@0:                      "Non prepending tokens should be handled in "
michael@0:                      "OnDataAvailable. There should only be a single "
michael@0:                      "prepending token left to be processed.");
michael@0:         (void)CatHTML(0, mBuffer.Length());
michael@0:     }
michael@0:     if (mPreFormatHTML) {
michael@0:         mBuffer.AppendLiteral("
\n"); michael@0: } michael@0: mBuffer.AppendLiteral("\n"); michael@0: michael@0: nsCOMPtr inputData; michael@0: michael@0: rv = NS_NewStringInputStream(getter_AddRefs(inputData), mBuffer); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = mListener->OnDataAvailable(request, aContext, michael@0: inputData, 0, mBuffer.Length()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return mListener->OnStopRequest(request, aContext, aStatus); michael@0: } michael@0: michael@0: // nsITXTToHTMLConv methods michael@0: NS_IMETHODIMP michael@0: nsTXTToHTMLConv::SetTitle(const char16_t *aTitle) michael@0: { michael@0: mPageTitle.Assign(aTitle); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTXTToHTMLConv::PreFormatHTML(bool value) michael@0: { michael@0: mPreFormatHTML = value; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsIStreamListener method michael@0: NS_IMETHODIMP michael@0: nsTXTToHTMLConv::OnDataAvailable(nsIRequest* request, nsISupports *aContext, michael@0: nsIInputStream *aInStream, michael@0: uint64_t aOffset, uint32_t aCount) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: nsString pushBuffer; michael@0: uint32_t amtRead = 0; michael@0: nsAutoArrayPtr buffer(new char[aCount+1]); michael@0: if (!buffer) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: do { michael@0: uint32_t read = 0; michael@0: // XXX readSegments, to avoid the first copy? michael@0: rv = aInStream->Read(buffer, aCount-amtRead, &read); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: buffer[read] = '\0'; michael@0: // XXX charsets?? non-latin1 characters?? utf-16?? michael@0: AppendASCIItoUTF16(buffer, mBuffer); michael@0: amtRead += read; michael@0: michael@0: int32_t front = -1, back = -1, tokenLoc = -1, cursor = 0; michael@0: michael@0: while ( (tokenLoc = FindToken(cursor, &mToken)) > -1) { michael@0: if (mToken->prepend) { michael@0: front = mBuffer.RFindCharInSet(TOKEN_DELIMITERS, tokenLoc); michael@0: front++; michael@0: back = mBuffer.FindCharInSet(TOKEN_DELIMITERS, tokenLoc); michael@0: } else { michael@0: front = tokenLoc; michael@0: back = front + mToken->token.Length(); michael@0: } michael@0: if (back == -1) { michael@0: // didn't find an ending, buffer up. michael@0: mBuffer.Left(pushBuffer, front); michael@0: cursor = front; michael@0: break; michael@0: } michael@0: // found the end of the token. michael@0: cursor = CatHTML(front, back); michael@0: } michael@0: michael@0: int32_t end = mBuffer.RFind(TOKEN_DELIMITERS, mBuffer.Length()); michael@0: mBuffer.Left(pushBuffer, std::max(cursor, end)); michael@0: mBuffer.Cut(0, std::max(cursor, end)); michael@0: cursor = 0; michael@0: michael@0: if (!pushBuffer.IsEmpty()) { michael@0: nsCOMPtr inputData; michael@0: michael@0: rv = NS_NewStringInputStream(getter_AddRefs(inputData), pushBuffer); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = mListener->OnDataAvailable(request, aContext, michael@0: inputData, 0, pushBuffer.Length()); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: } while (amtRead < aCount); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // nsTXTToHTMLConv methods michael@0: nsTXTToHTMLConv::nsTXTToHTMLConv() michael@0: { michael@0: mToken = nullptr; michael@0: mPreFormatHTML = false; michael@0: } michael@0: michael@0: nsTXTToHTMLConv::~nsTXTToHTMLConv() michael@0: { michael@0: mTokens.Clear(); michael@0: } michael@0: michael@0: nsresult michael@0: nsTXTToHTMLConv::Init() michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: // build up the list of tokens to handle michael@0: convToken *token = new convToken; michael@0: if (!token) return NS_ERROR_OUT_OF_MEMORY; michael@0: token->prepend = false; michael@0: token->token.Assign(char16_t('<')); michael@0: token->modText.AssignLiteral("<"); michael@0: mTokens.AppendElement(token); michael@0: michael@0: token = new convToken; michael@0: if (!token) return NS_ERROR_OUT_OF_MEMORY; michael@0: token->prepend = false; michael@0: token->token.Assign(char16_t('>')); michael@0: token->modText.AssignLiteral(">"); michael@0: mTokens.AppendElement(token); michael@0: michael@0: token = new convToken; michael@0: if (!token) return NS_ERROR_OUT_OF_MEMORY; michael@0: token->prepend = false; michael@0: token->token.Assign(char16_t('&')); michael@0: token->modText.AssignLiteral("&"); michael@0: mTokens.AppendElement(token); michael@0: michael@0: token = new convToken; michael@0: if (!token) return NS_ERROR_OUT_OF_MEMORY; michael@0: token->prepend = true; michael@0: token->token.AssignLiteral("http://"); // XXX need to iterate through all protos michael@0: mTokens.AppendElement(token); michael@0: michael@0: token = new convToken; michael@0: if (!token) return NS_ERROR_OUT_OF_MEMORY; michael@0: token->prepend = true; michael@0: token->token.Assign(char16_t('@')); michael@0: token->modText.AssignLiteral("mailto:"); michael@0: mTokens.AppendElement(token); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: int32_t michael@0: nsTXTToHTMLConv::FindToken(int32_t cursor, convToken* *_retval) michael@0: { michael@0: int32_t loc = -1, firstToken = mBuffer.Length(); michael@0: int8_t token = -1; michael@0: for (uint8_t i=0; i < mTokens.Length(); i++) { michael@0: loc = mBuffer.Find(mTokens[i]->token, cursor); michael@0: if (loc != -1) michael@0: if (loc < firstToken) { michael@0: firstToken = loc; michael@0: token = i; michael@0: } michael@0: } michael@0: if (token == -1) michael@0: return -1; michael@0: michael@0: *_retval = mTokens[token]; michael@0: return firstToken; michael@0: } michael@0: michael@0: int32_t michael@0: nsTXTToHTMLConv::CatHTML(int32_t front, int32_t back) michael@0: { michael@0: int32_t cursor = 0; michael@0: int32_t modLen = mToken->modText.Length(); michael@0: if (!mToken->prepend) { michael@0: // replace the entire token (from delimiter to delimiter) michael@0: mBuffer.Cut(front, back - front); michael@0: mBuffer.Insert(mToken->modText, front); michael@0: cursor = front+modLen; michael@0: } else { michael@0: nsString linkText; michael@0: // href is implied michael@0: mBuffer.Mid(linkText, front, back-front); michael@0: michael@0: mBuffer.Insert(NS_LITERAL_STRING("modText, cursor); michael@0: cursor += modLen; michael@0: } michael@0: michael@0: NS_ConvertUTF16toUTF8 linkTextUTF8(linkText); michael@0: nsCString escaped; michael@0: if (NS_EscapeURL(linkTextUTF8.Data(), linkTextUTF8.Length(), esc_Minimal, escaped)) { michael@0: mBuffer.Cut(cursor, back - front); michael@0: CopyUTF8toUTF16(escaped, linkText); michael@0: mBuffer.Insert(linkText, cursor); michael@0: back = front + linkText.Length(); michael@0: } michael@0: michael@0: cursor += back-front; michael@0: mBuffer.Insert(NS_LITERAL_STRING("\">"), cursor); michael@0: cursor += 2; michael@0: mBuffer.Insert(linkText, cursor); michael@0: cursor += linkText.Length(); michael@0: mBuffer.Insert(NS_LITERAL_STRING(""), cursor); michael@0: cursor += 4; michael@0: } michael@0: mToken = nullptr; // indicates completeness michael@0: return cursor; michael@0: }