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("<");
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(">");
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("&");
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