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