|
1 /* -*- Mode: C++; tab-width: 2; 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 "nsFTPDirListingConv.h" |
|
7 #include "nsMemory.h" |
|
8 #include "plstr.h" |
|
9 #include "prlog.h" |
|
10 #include "nsCOMPtr.h" |
|
11 #include "nsEscape.h" |
|
12 #include "nsStringStream.h" |
|
13 #include "nsIStreamListener.h" |
|
14 #include "nsCRT.h" |
|
15 #include "nsAutoPtr.h" |
|
16 #include "nsIChannel.h" |
|
17 #include "nsIURI.h" |
|
18 |
|
19 #include "ParseFTPList.h" |
|
20 #include <algorithm> |
|
21 |
|
22 #if defined(PR_LOGGING) |
|
23 // |
|
24 // Log module for FTP dir listing stream converter logging... |
|
25 // |
|
26 // To enable logging (see prlog.h for full details): |
|
27 // |
|
28 // set NSPR_LOG_MODULES=nsFTPDirListConv:5 |
|
29 // set NSPR_LOG_FILE=nspr.log |
|
30 // |
|
31 // this enables PR_LOG_DEBUG level information and places all output in |
|
32 // the file nspr.log |
|
33 // |
|
34 PRLogModuleInfo* gFTPDirListConvLog = nullptr; |
|
35 |
|
36 #endif /* PR_LOGGING */ |
|
37 |
|
38 // nsISupports implementation |
|
39 NS_IMPL_ISUPPORTS(nsFTPDirListingConv, |
|
40 nsIStreamConverter, |
|
41 nsIStreamListener, |
|
42 nsIRequestObserver) |
|
43 |
|
44 |
|
45 // nsIStreamConverter implementation |
|
46 NS_IMETHODIMP |
|
47 nsFTPDirListingConv::Convert(nsIInputStream *aFromStream, |
|
48 const char *aFromType, |
|
49 const char *aToType, |
|
50 nsISupports *aCtxt, nsIInputStream **_retval) { |
|
51 return NS_ERROR_NOT_IMPLEMENTED; |
|
52 } |
|
53 |
|
54 |
|
55 // Stream converter service calls this to initialize the actual stream converter (us). |
|
56 NS_IMETHODIMP |
|
57 nsFTPDirListingConv::AsyncConvertData(const char *aFromType, const char *aToType, |
|
58 nsIStreamListener *aListener, nsISupports *aCtxt) { |
|
59 NS_ASSERTION(aListener && aFromType && aToType, "null pointer passed into FTP dir listing converter"); |
|
60 |
|
61 // hook up our final listener. this guy gets the various On*() calls we want to throw |
|
62 // at him. |
|
63 mFinalListener = aListener; |
|
64 NS_ADDREF(mFinalListener); |
|
65 |
|
66 PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, |
|
67 ("nsFTPDirListingConv::AsyncConvertData() converting FROM raw, TO application/http-index-format\n")); |
|
68 |
|
69 return NS_OK; |
|
70 } |
|
71 |
|
72 |
|
73 // nsIStreamListener implementation |
|
74 NS_IMETHODIMP |
|
75 nsFTPDirListingConv::OnDataAvailable(nsIRequest* request, nsISupports *ctxt, |
|
76 nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count) { |
|
77 NS_ASSERTION(request, "FTP dir listing stream converter needs a request"); |
|
78 |
|
79 nsresult rv; |
|
80 |
|
81 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv); |
|
82 NS_ENSURE_SUCCESS(rv, rv); |
|
83 |
|
84 uint32_t read, streamLen; |
|
85 |
|
86 uint64_t streamLen64; |
|
87 rv = inStr->Available(&streamLen64); |
|
88 NS_ENSURE_SUCCESS(rv, rv); |
|
89 streamLen = (uint32_t)std::min(streamLen64, uint64_t(UINT32_MAX - 1)); |
|
90 |
|
91 nsAutoArrayPtr<char> buffer(new char[streamLen + 1]); |
|
92 NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY); |
|
93 |
|
94 rv = inStr->Read(buffer, streamLen, &read); |
|
95 NS_ENSURE_SUCCESS(rv, rv); |
|
96 |
|
97 // the dir listings are ascii text, null terminate this sucker. |
|
98 buffer[streamLen] = '\0'; |
|
99 |
|
100 PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("nsFTPDirListingConv::OnData(request = %x, ctxt = %x, inStr = %x, sourceOffset = %llu, count = %u)\n", request, ctxt, inStr, sourceOffset, count)); |
|
101 |
|
102 if (!mBuffer.IsEmpty()) { |
|
103 // we have data left over from a previous OnDataAvailable() call. |
|
104 // combine the buffers so we don't lose any data. |
|
105 mBuffer.Append(buffer); |
|
106 |
|
107 buffer = new char[mBuffer.Length()+1]; |
|
108 NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY); |
|
109 |
|
110 strncpy(buffer, mBuffer.get(), mBuffer.Length()+1); |
|
111 mBuffer.Truncate(); |
|
112 } |
|
113 |
|
114 #ifndef DEBUG_dougt |
|
115 PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() received the following %d bytes...\n\n%s\n\n", streamLen, buffer.get()) ); |
|
116 #else |
|
117 printf("::OnData() received the following %d bytes...\n\n%s\n\n", streamLen, buffer); |
|
118 #endif // DEBUG_dougt |
|
119 |
|
120 nsAutoCString indexFormat; |
|
121 if (!mSentHeading) { |
|
122 // build up the 300: line |
|
123 nsCOMPtr<nsIURI> uri; |
|
124 rv = channel->GetURI(getter_AddRefs(uri)); |
|
125 NS_ENSURE_SUCCESS(rv, rv); |
|
126 |
|
127 rv = GetHeaders(indexFormat, uri); |
|
128 NS_ENSURE_SUCCESS(rv, rv); |
|
129 |
|
130 mSentHeading = true; |
|
131 } |
|
132 |
|
133 char *line = buffer; |
|
134 line = DigestBufferLines(line, indexFormat); |
|
135 |
|
136 #ifndef DEBUG_dougt |
|
137 PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() sending the following %d bytes...\n\n%s\n\n", |
|
138 indexFormat.Length(), indexFormat.get()) ); |
|
139 #else |
|
140 char *unescData = ToNewCString(indexFormat); |
|
141 NS_ENSURE_TRUE(unescData, NS_ERROR_OUT_OF_MEMORY); |
|
142 |
|
143 nsUnescape(unescData); |
|
144 printf("::OnData() sending the following %d bytes...\n\n%s\n\n", indexFormat.Length(), unescData); |
|
145 nsMemory::Free(unescData); |
|
146 #endif // DEBUG_dougt |
|
147 |
|
148 // if there's any data left over, buffer it. |
|
149 if (line && *line) { |
|
150 mBuffer.Append(line); |
|
151 PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() buffering the following %d bytes...\n\n%s\n\n", |
|
152 strlen(line), line) ); |
|
153 } |
|
154 |
|
155 // send the converted data out. |
|
156 nsCOMPtr<nsIInputStream> inputData; |
|
157 |
|
158 rv = NS_NewCStringInputStream(getter_AddRefs(inputData), indexFormat); |
|
159 NS_ENSURE_SUCCESS(rv, rv); |
|
160 |
|
161 rv = mFinalListener->OnDataAvailable(request, ctxt, inputData, 0, indexFormat.Length()); |
|
162 |
|
163 return rv; |
|
164 } |
|
165 |
|
166 |
|
167 // nsIRequestObserver implementation |
|
168 NS_IMETHODIMP |
|
169 nsFTPDirListingConv::OnStartRequest(nsIRequest* request, nsISupports *ctxt) { |
|
170 // we don't care about start. move along... but start masqeurading |
|
171 // as the http-index channel now. |
|
172 return mFinalListener->OnStartRequest(request, ctxt); |
|
173 } |
|
174 |
|
175 NS_IMETHODIMP |
|
176 nsFTPDirListingConv::OnStopRequest(nsIRequest* request, nsISupports *ctxt, |
|
177 nsresult aStatus) { |
|
178 // we don't care about stop. move along... |
|
179 |
|
180 return mFinalListener->OnStopRequest(request, ctxt, aStatus); |
|
181 } |
|
182 |
|
183 |
|
184 // nsFTPDirListingConv methods |
|
185 nsFTPDirListingConv::nsFTPDirListingConv() { |
|
186 mFinalListener = nullptr; |
|
187 mSentHeading = false; |
|
188 } |
|
189 |
|
190 nsFTPDirListingConv::~nsFTPDirListingConv() { |
|
191 NS_IF_RELEASE(mFinalListener); |
|
192 } |
|
193 |
|
194 nsresult |
|
195 nsFTPDirListingConv::Init() { |
|
196 #if defined(PR_LOGGING) |
|
197 // |
|
198 // Initialize the global PRLogModule for FTP Protocol logging |
|
199 // if necessary... |
|
200 // |
|
201 if (nullptr == gFTPDirListConvLog) { |
|
202 gFTPDirListConvLog = PR_NewLogModule("nsFTPDirListingConv"); |
|
203 } |
|
204 #endif /* PR_LOGGING */ |
|
205 |
|
206 return NS_OK; |
|
207 } |
|
208 |
|
209 nsresult |
|
210 nsFTPDirListingConv::GetHeaders(nsACString& headers, |
|
211 nsIURI* uri) |
|
212 { |
|
213 nsresult rv = NS_OK; |
|
214 // build up 300 line |
|
215 headers.AppendLiteral("300: "); |
|
216 |
|
217 // Bug 111117 - don't print the password |
|
218 nsAutoCString pw; |
|
219 nsAutoCString spec; |
|
220 uri->GetPassword(pw); |
|
221 if (!pw.IsEmpty()) { |
|
222 rv = uri->SetPassword(EmptyCString()); |
|
223 if (NS_FAILED(rv)) return rv; |
|
224 rv = uri->GetAsciiSpec(spec); |
|
225 if (NS_FAILED(rv)) return rv; |
|
226 headers.Append(spec); |
|
227 rv = uri->SetPassword(pw); |
|
228 if (NS_FAILED(rv)) return rv; |
|
229 } else { |
|
230 rv = uri->GetAsciiSpec(spec); |
|
231 if (NS_FAILED(rv)) return rv; |
|
232 |
|
233 headers.Append(spec); |
|
234 } |
|
235 headers.Append(char(nsCRT::LF)); |
|
236 // END 300: |
|
237 |
|
238 // build up the column heading; 200: |
|
239 headers.AppendLiteral("200: filename content-length last-modified file-type\n"); |
|
240 // END 200: |
|
241 return rv; |
|
242 } |
|
243 |
|
244 char * |
|
245 nsFTPDirListingConv::DigestBufferLines(char *aBuffer, nsCString &aString) { |
|
246 char *line = aBuffer; |
|
247 char *eol; |
|
248 bool cr = false; |
|
249 |
|
250 list_state state; |
|
251 |
|
252 // while we have new lines, parse 'em into application/http-index-format. |
|
253 while ( line && (eol = PL_strchr(line, nsCRT::LF)) ) { |
|
254 // yank any carriage returns too. |
|
255 if (eol > line && *(eol-1) == nsCRT::CR) { |
|
256 eol--; |
|
257 *eol = '\0'; |
|
258 cr = true; |
|
259 } else { |
|
260 *eol = '\0'; |
|
261 cr = false; |
|
262 } |
|
263 |
|
264 list_result result; |
|
265 |
|
266 int type = ParseFTPList(line, &state, &result ); |
|
267 |
|
268 // if it is other than a directory, file, or link -OR- if it is a |
|
269 // directory named . or .., skip over this line. |
|
270 if ((type != 'd' && type != 'f' && type != 'l') || |
|
271 (result.fe_type == 'd' && result.fe_fname[0] == '.' && |
|
272 (result.fe_fnlen == 1 || (result.fe_fnlen == 2 && result.fe_fname[1] == '.'))) ) |
|
273 { |
|
274 if (cr) |
|
275 line = eol+2; |
|
276 else |
|
277 line = eol+1; |
|
278 |
|
279 continue; |
|
280 } |
|
281 |
|
282 // blast the index entry into the indexFormat buffer as a 201: line. |
|
283 aString.AppendLiteral("201: "); |
|
284 // FILENAME |
|
285 |
|
286 // parsers for styles 'U' and 'W' handle sequence " -> " themself |
|
287 if (state.lstyle != 'U' && state.lstyle != 'W') { |
|
288 const char* offset = strstr(result.fe_fname, " -> "); |
|
289 if (offset) { |
|
290 result.fe_fnlen = offset - result.fe_fname; |
|
291 } |
|
292 } |
|
293 |
|
294 nsAutoCString buf; |
|
295 aString.Append('\"'); |
|
296 aString.Append(NS_EscapeURL(Substring(result.fe_fname, |
|
297 result.fe_fname+result.fe_fnlen), |
|
298 esc_Minimal|esc_OnlyASCII|esc_Forced,buf)); |
|
299 aString.AppendLiteral("\" "); |
|
300 |
|
301 // CONTENT LENGTH |
|
302 |
|
303 if (type != 'd') |
|
304 { |
|
305 for (int i = 0; i < int(sizeof(result.fe_size)); ++i) |
|
306 { |
|
307 if (result.fe_size[i] != '\0') |
|
308 aString.Append((const char*)&result.fe_size[i], 1); |
|
309 } |
|
310 |
|
311 aString.Append(' '); |
|
312 } |
|
313 else |
|
314 aString.AppendLiteral("0 "); |
|
315 |
|
316 |
|
317 // MODIFIED DATE |
|
318 char buffer[256] = ""; |
|
319 // Note: The below is the RFC822/1123 format, as required by |
|
320 // the application/http-index-format specs |
|
321 // viewers of such a format can then reformat this into the |
|
322 // current locale (or anything else they choose) |
|
323 PR_FormatTimeUSEnglish(buffer, sizeof(buffer), |
|
324 "%a, %d %b %Y %H:%M:%S", &result.fe_time ); |
|
325 |
|
326 char *escapedDate = nsEscape(buffer, url_Path); |
|
327 aString.Append(escapedDate); |
|
328 nsMemory::Free(escapedDate); |
|
329 aString.Append(' '); |
|
330 |
|
331 // ENTRY TYPE |
|
332 if (type == 'd') |
|
333 aString.AppendLiteral("DIRECTORY"); |
|
334 else if (type == 'l') |
|
335 aString.AppendLiteral("SYMBOLIC-LINK"); |
|
336 else |
|
337 aString.AppendLiteral("FILE"); |
|
338 |
|
339 aString.Append(' '); |
|
340 |
|
341 aString.Append(char(nsCRT::LF)); // complete this line |
|
342 // END 201: |
|
343 |
|
344 if (cr) |
|
345 line = eol+2; |
|
346 else |
|
347 line = eol+1; |
|
348 } // end while(eol) |
|
349 |
|
350 return line; |
|
351 } |
|
352 |
|
353 nsresult |
|
354 NS_NewFTPDirListingConv(nsFTPDirListingConv** aFTPDirListingConv) |
|
355 { |
|
356 NS_PRECONDITION(aFTPDirListingConv != nullptr, "null ptr"); |
|
357 if (! aFTPDirListingConv) |
|
358 return NS_ERROR_NULL_POINTER; |
|
359 |
|
360 *aFTPDirListingConv = new nsFTPDirListingConv(); |
|
361 if (! *aFTPDirListingConv) |
|
362 return NS_ERROR_OUT_OF_MEMORY; |
|
363 |
|
364 NS_ADDREF(*aFTPDirListingConv); |
|
365 return (*aFTPDirListingConv)->Init(); |
|
366 } |