1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsTXTToHTMLConv.h"
7 #include "nsEscape.h"
8 #include "nsStringStream.h"
9 #include "nsAutoPtr.h"
10 #include "nsIChannel.h"
11 #include <algorithm>
12 
13 #include "mozilla/UniquePtrExtensions.h"
14 
15 #define TOKEN_DELIMITERS u"\t\r\n "
16 
17 using namespace mozilla;
18 
19 // nsISupports methods
NS_IMPL_ISUPPORTS(nsTXTToHTMLConv,nsIStreamConverter,nsITXTToHTMLConv,nsIRequestObserver,nsIStreamListener)20 NS_IMPL_ISUPPORTS(nsTXTToHTMLConv,
21                   nsIStreamConverter,
22                   nsITXTToHTMLConv,
23                   nsIRequestObserver,
24                   nsIStreamListener)
25 
26 
27 // nsIStreamConverter methods
28 NS_IMETHODIMP
29 nsTXTToHTMLConv::Convert(nsIInputStream *aFromStream,
30                          const char *aFromType, const char *aToType,
31                          nsISupports *aCtxt, nsIInputStream * *_retval)
32 {
33     return NS_ERROR_NOT_IMPLEMENTED;
34 }
35 
36 NS_IMETHODIMP
AsyncConvertData(const char * aFromType,const char * aToType,nsIStreamListener * aListener,nsISupports * aCtxt)37 nsTXTToHTMLConv::AsyncConvertData(const char *aFromType,
38                                   const char *aToType,
39                                   nsIStreamListener *aListener,
40                                   nsISupports *aCtxt)
41 {
42     NS_ASSERTION(aListener, "null pointer");
43     mListener = aListener;
44     return NS_OK;
45 }
46 
47 
48 // nsIRequestObserver methods
49 NS_IMETHODIMP
OnStartRequest(nsIRequest * request,nsISupports * aContext)50 nsTXTToHTMLConv::OnStartRequest(nsIRequest* request, nsISupports *aContext)
51 {
52     mBuffer.AssignLiteral("<html>\n<head><title>");
53     mBuffer.Append(mPageTitle);
54     mBuffer.AppendLiteral("</title></head>\n<body>\n");
55     if (mPreFormatHTML) {     // Use <pre> tags
56         mBuffer.AppendLiteral("<pre>\n");
57     }
58 
59     // Push mBuffer to the listener now, so the initial HTML will not
60     // be parsed in OnDataAvailable().
61 
62     nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
63     if (channel)
64         channel->SetContentType(NS_LITERAL_CSTRING("text/html"));
65     // else, assume there is a channel somewhere that knows what it is doing!
66 
67     nsresult rv = mListener->OnStartRequest(request, aContext);
68     if (NS_FAILED(rv)) return rv;
69 
70     // The request may have been canceled, and if that happens, we want to
71     // suppress calls to OnDataAvailable.
72     request->GetStatus(&rv);
73     if (NS_FAILED(rv)) return rv;
74 
75     nsCOMPtr<nsIInputStream> inputData;
76     NS_LossyConvertUTF16toASCII asciiData(mBuffer);
77     rv = NS_NewCStringInputStream(getter_AddRefs(inputData), asciiData);
78     if (NS_FAILED(rv)) return rv;
79 
80     rv = mListener->OnDataAvailable(request, aContext,
81                                     inputData, 0, mBuffer.Length());
82     if (NS_FAILED(rv)) return rv;
83     mBuffer.Truncate();
84     return rv;
85 }
86 
87 NS_IMETHODIMP
OnStopRequest(nsIRequest * request,nsISupports * aContext,nsresult aStatus)88 nsTXTToHTMLConv::OnStopRequest(nsIRequest* request, nsISupports *aContext,
89                                nsresult aStatus)
90 {
91     nsresult rv = NS_OK;
92     if (mToken) {
93         // we still have an outstanding token
94         NS_ASSERTION(mToken->prepend,
95                      "Non prepending tokens should be handled in "
96                      "OnDataAvailable. There should only be a single "
97                      "prepending token left to be processed.");
98         (void)CatHTML(0, mBuffer.Length());
99     }
100     if (mPreFormatHTML) {
101         mBuffer.AppendLiteral("</pre>\n");
102     }
103     mBuffer.AppendLiteral("\n</body></html>");
104 
105     nsCOMPtr<nsIInputStream> inputData;
106     NS_LossyConvertUTF16toASCII asciiData(mBuffer);
107     rv = NS_NewCStringInputStream(getter_AddRefs(inputData), asciiData);
108     if (NS_FAILED(rv)) return rv;
109 
110     rv = mListener->OnDataAvailable(request, aContext,
111                                     inputData, 0, mBuffer.Length());
112     if (NS_FAILED(rv)) return rv;
113 
114     return mListener->OnStopRequest(request, aContext, aStatus);
115 }
116 
117 // nsITXTToHTMLConv methods
118 NS_IMETHODIMP
SetTitle(const char16_t * aTitle)119 nsTXTToHTMLConv::SetTitle(const char16_t *aTitle)
120 {
121     mPageTitle.Assign(aTitle);
122     return NS_OK;
123 }
124 
125 NS_IMETHODIMP
PreFormatHTML(bool value)126 nsTXTToHTMLConv::PreFormatHTML(bool value)
127 {
128     mPreFormatHTML = value;
129     return NS_OK;
130 }
131 
132 // nsIStreamListener method
133 NS_IMETHODIMP
OnDataAvailable(nsIRequest * request,nsISupports * aContext,nsIInputStream * aInStream,uint64_t aOffset,uint32_t aCount)134 nsTXTToHTMLConv::OnDataAvailable(nsIRequest* request, nsISupports *aContext,
135                                  nsIInputStream *aInStream,
136                                  uint64_t aOffset, uint32_t aCount)
137 {
138     nsresult rv = NS_OK;
139     nsString pushBuffer;
140     uint32_t amtRead = 0;
141     auto buffer = MakeUniqueFallible<char[]>(aCount+1);
142     if (!buffer) return NS_ERROR_OUT_OF_MEMORY;
143 
144     do {
145         uint32_t read = 0;
146         // XXX readSegments, to avoid the first copy?
147         rv = aInStream->Read(buffer.get(), aCount-amtRead, &read);
148         if (NS_FAILED(rv)) return rv;
149 
150         buffer[read] = '\0';
151         // XXX charsets?? non-latin1 characters?? utf-16??
152         AppendASCIItoUTF16(buffer.get(), mBuffer);
153         amtRead += read;
154 
155         int32_t front = -1, back = -1, tokenLoc = -1, cursor = 0;
156 
157         while ( (tokenLoc = FindToken(cursor, &mToken)) > -1) {
158             if (mToken->prepend) {
159                 front = mBuffer.RFindCharInSet(TOKEN_DELIMITERS, tokenLoc);
160                 front++;
161                 back = mBuffer.FindCharInSet(TOKEN_DELIMITERS, tokenLoc);
162             } else {
163                 front = tokenLoc;
164                 back = front + mToken->token.Length();
165             }
166             if (back == -1) {
167                 // didn't find an ending, buffer up.
168                 mBuffer.Left(pushBuffer, front);
169                 cursor = front;
170                 break;
171             }
172             // found the end of the token.
173             cursor = CatHTML(front, back);
174         }
175 
176         int32_t end = mBuffer.RFind(TOKEN_DELIMITERS, mBuffer.Length());
177         mBuffer.Left(pushBuffer, std::max(cursor, end));
178         mBuffer.Cut(0, std::max(cursor, end));
179         cursor = 0;
180 
181         if (!pushBuffer.IsEmpty()) {
182             nsCOMPtr<nsIInputStream> inputData;
183             NS_LossyConvertUTF16toASCII asciiData(pushBuffer);
184             rv = NS_NewCStringInputStream(getter_AddRefs(inputData), asciiData);
185             if (NS_FAILED(rv))
186                 return rv;
187 
188             rv = mListener->OnDataAvailable(request, aContext,
189                                             inputData, 0, pushBuffer.Length());
190             if (NS_FAILED(rv))
191                 return rv;
192         }
193     } while (amtRead < aCount);
194 
195     return rv;
196 }
197 
198 // nsTXTToHTMLConv methods
nsTXTToHTMLConv()199 nsTXTToHTMLConv::nsTXTToHTMLConv()
200 {
201     mToken = nullptr;
202     mPreFormatHTML = false;
203 }
204 
~nsTXTToHTMLConv()205 nsTXTToHTMLConv::~nsTXTToHTMLConv()
206 {
207     mTokens.Clear();
208 }
209 
210 nsresult
Init()211 nsTXTToHTMLConv::Init()
212 {
213     nsresult rv = NS_OK;
214 
215     // build up the list of tokens to handle
216     convToken *token = new convToken;
217     if (!token) return NS_ERROR_OUT_OF_MEMORY;
218     token->prepend = false;
219     token->token.Assign(char16_t('<'));
220     token->modText.AssignLiteral("&lt;");
221     mTokens.AppendElement(token);
222 
223     token = new convToken;
224     if (!token) return NS_ERROR_OUT_OF_MEMORY;
225     token->prepend = false;
226     token->token.Assign(char16_t('>'));
227     token->modText.AssignLiteral("&gt;");
228     mTokens.AppendElement(token);
229 
230     token = new convToken;
231     if (!token) return NS_ERROR_OUT_OF_MEMORY;
232     token->prepend = false;
233     token->token.Assign(char16_t('&'));
234     token->modText.AssignLiteral("&amp;");
235     mTokens.AppendElement(token);
236 
237     token = new convToken;
238     if (!token) return NS_ERROR_OUT_OF_MEMORY;
239     token->prepend = true;
240     token->token.AssignLiteral("http://"); // XXX need to iterate through all protos
241     mTokens.AppendElement(token);
242 
243     token = new convToken;
244     if (!token) return NS_ERROR_OUT_OF_MEMORY;
245     token->prepend = true;
246     token->token.Assign(char16_t('@'));
247     token->modText.AssignLiteral("mailto:");
248     mTokens.AppendElement(token);
249 
250     return rv;
251 }
252 
253 int32_t
FindToken(int32_t cursor,convToken ** _retval)254 nsTXTToHTMLConv::FindToken(int32_t cursor, convToken* *_retval)
255 {
256     int32_t loc = -1, firstToken = mBuffer.Length();
257     int8_t token = -1;
258     for (uint8_t i=0; i < mTokens.Length(); i++) {
259         loc = mBuffer.Find(mTokens[i]->token, cursor);
260         if (loc != -1)
261             if (loc < firstToken) {
262                 firstToken = loc;
263                 token = i;
264             }
265     }
266     if (token == -1)
267         return -1;
268 
269     *_retval = mTokens[token];
270     return firstToken;
271 }
272 
273 int32_t
CatHTML(int32_t front,int32_t back)274 nsTXTToHTMLConv::CatHTML(int32_t front, int32_t back)
275 {
276     int32_t cursor = 0;
277     int32_t modLen = mToken->modText.Length();
278     if (!mToken->prepend) {
279         // replace the entire token (from delimiter to delimiter)
280         mBuffer.Cut(front, back - front);
281         mBuffer.Insert(mToken->modText, front);
282         cursor = front+modLen;
283     } else {
284         nsString linkText;
285         // href is implied
286         mBuffer.Mid(linkText, front, back-front);
287 
288         mBuffer.Insert(NS_LITERAL_STRING("<a href=\""), front);
289         cursor += front+9;
290         if (modLen) {
291             mBuffer.Insert(mToken->modText, cursor);
292             cursor += modLen;
293         }
294 
295         NS_ConvertUTF16toUTF8 linkTextUTF8(linkText);
296         nsCString escaped;
297         if (NS_EscapeURL(linkTextUTF8.Data(), linkTextUTF8.Length(), esc_Minimal, escaped)) {
298             mBuffer.Cut(cursor, back - front);
299             CopyUTF8toUTF16(escaped, linkText);
300             mBuffer.Insert(linkText, cursor);
301             back = front + linkText.Length();
302         }
303 
304         cursor += back-front;
305         mBuffer.Insert(NS_LITERAL_STRING("\">"), cursor);
306         cursor += 2;
307         mBuffer.Insert(linkText, cursor);
308         cursor += linkText.Length();
309         mBuffer.Insert(NS_LITERAL_STRING("</a>"), cursor);
310         cursor += 4;
311     }
312     mToken = nullptr; // indicates completeness
313     return cursor;
314 }
315