|
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 "nsIOService.h" |
|
7 #include "nsFtpControlConnection.h" |
|
8 #include "nsFtpProtocolHandler.h" |
|
9 #include "prlog.h" |
|
10 #include "nsIInputStream.h" |
|
11 #include "nsISocketTransportService.h" |
|
12 #include "nsISocketTransport.h" |
|
13 #include "nsThreadUtils.h" |
|
14 #include "nsIOutputStream.h" |
|
15 #include "nsNetCID.h" |
|
16 #include <algorithm> |
|
17 |
|
18 #if defined(PR_LOGGING) |
|
19 extern PRLogModuleInfo* gFTPLog; |
|
20 #endif |
|
21 #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args) |
|
22 #define LOG_ALWAYS(args) PR_LOG(gFTPLog, PR_LOG_ALWAYS, args) |
|
23 |
|
24 // |
|
25 // nsFtpControlConnection implementation ... |
|
26 // |
|
27 |
|
28 NS_IMPL_ISUPPORTS(nsFtpControlConnection, nsIInputStreamCallback) |
|
29 |
|
30 NS_IMETHODIMP |
|
31 nsFtpControlConnection::OnInputStreamReady(nsIAsyncInputStream *stream) |
|
32 { |
|
33 char data[4096]; |
|
34 |
|
35 // Consume data whether we have a listener or not. |
|
36 uint64_t avail64; |
|
37 uint32_t avail = 0; |
|
38 nsresult rv = stream->Available(&avail64); |
|
39 if (NS_SUCCEEDED(rv)) { |
|
40 avail = (uint32_t)std::min(avail64, (uint64_t)sizeof(data)); |
|
41 |
|
42 uint32_t n; |
|
43 rv = stream->Read(data, avail, &n); |
|
44 if (NS_SUCCEEDED(rv)) |
|
45 avail = n; |
|
46 } |
|
47 |
|
48 // It's important that we null out mListener before calling one of its |
|
49 // methods as it may call WaitData, which would queue up another read. |
|
50 |
|
51 nsRefPtr<nsFtpControlConnectionListener> listener; |
|
52 listener.swap(mListener); |
|
53 |
|
54 if (!listener) |
|
55 return NS_OK; |
|
56 |
|
57 if (NS_FAILED(rv)) { |
|
58 listener->OnControlError(rv); |
|
59 } else { |
|
60 listener->OnControlDataAvailable(data, avail); |
|
61 } |
|
62 |
|
63 return NS_OK; |
|
64 } |
|
65 |
|
66 nsFtpControlConnection::nsFtpControlConnection(const nsCSubstring& host, |
|
67 uint32_t port) |
|
68 : mServerType(0), mSessionId(gFtpHandler->GetSessionId()) |
|
69 , mUseUTF8(false), mHost(host), mPort(port) |
|
70 { |
|
71 LOG_ALWAYS(("FTP:CC created @%p", this)); |
|
72 } |
|
73 |
|
74 nsFtpControlConnection::~nsFtpControlConnection() |
|
75 { |
|
76 LOG_ALWAYS(("FTP:CC destroyed @%p", this)); |
|
77 } |
|
78 |
|
79 bool |
|
80 nsFtpControlConnection::IsAlive() |
|
81 { |
|
82 if (!mSocket) |
|
83 return false; |
|
84 |
|
85 bool isAlive = false; |
|
86 mSocket->IsAlive(&isAlive); |
|
87 return isAlive; |
|
88 } |
|
89 nsresult |
|
90 nsFtpControlConnection::Connect(nsIProxyInfo* proxyInfo, |
|
91 nsITransportEventSink* eventSink) |
|
92 { |
|
93 if (mSocket) |
|
94 return NS_OK; |
|
95 |
|
96 // build our own |
|
97 nsresult rv; |
|
98 nsCOMPtr<nsISocketTransportService> sts = |
|
99 do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); |
|
100 if (NS_FAILED(rv)) |
|
101 return rv; |
|
102 |
|
103 rv = sts->CreateTransport(nullptr, 0, mHost, mPort, proxyInfo, |
|
104 getter_AddRefs(mSocket)); // the command transport |
|
105 if (NS_FAILED(rv)) |
|
106 return rv; |
|
107 |
|
108 mSocket->SetQoSBits(gFtpHandler->GetControlQoSBits()); |
|
109 |
|
110 // proxy transport events back to current thread |
|
111 if (eventSink) |
|
112 mSocket->SetEventSink(eventSink, NS_GetCurrentThread()); |
|
113 |
|
114 // open buffered, blocking output stream to socket. so long as commands |
|
115 // do not exceed 1024 bytes in length, the writing thread (the main thread) |
|
116 // will not block. this should be OK. |
|
117 rv = mSocket->OpenOutputStream(nsITransport::OPEN_BLOCKING, 1024, 1, |
|
118 getter_AddRefs(mSocketOutput)); |
|
119 if (NS_FAILED(rv)) |
|
120 return rv; |
|
121 |
|
122 // open buffered, non-blocking/asynchronous input stream to socket. |
|
123 nsCOMPtr<nsIInputStream> inStream; |
|
124 rv = mSocket->OpenInputStream(0, |
|
125 nsIOService::gDefaultSegmentSize, |
|
126 nsIOService::gDefaultSegmentCount, |
|
127 getter_AddRefs(inStream)); |
|
128 if (NS_SUCCEEDED(rv)) |
|
129 mSocketInput = do_QueryInterface(inStream); |
|
130 |
|
131 return rv; |
|
132 } |
|
133 |
|
134 nsresult |
|
135 nsFtpControlConnection::WaitData(nsFtpControlConnectionListener *listener) |
|
136 { |
|
137 LOG(("FTP:(%p) wait data [listener=%p]\n", this, listener)); |
|
138 |
|
139 // If listener is null, then simply disconnect the listener. Otherwise, |
|
140 // ensure that we are listening. |
|
141 if (!listener) { |
|
142 mListener = nullptr; |
|
143 return NS_OK; |
|
144 } |
|
145 |
|
146 NS_ENSURE_STATE(mSocketInput); |
|
147 |
|
148 mListener = listener; |
|
149 return mSocketInput->AsyncWait(this, 0, 0, NS_GetCurrentThread()); |
|
150 } |
|
151 |
|
152 nsresult |
|
153 nsFtpControlConnection::Disconnect(nsresult status) |
|
154 { |
|
155 if (!mSocket) |
|
156 return NS_OK; // already disconnected |
|
157 |
|
158 LOG_ALWAYS(("FTP:(%p) CC disconnecting (%x)", this, status)); |
|
159 |
|
160 if (NS_FAILED(status)) { |
|
161 // break cyclic reference! |
|
162 mSocket->Close(status); |
|
163 mSocket = 0; |
|
164 mSocketInput->AsyncWait(nullptr, 0, 0, nullptr); // clear any observer |
|
165 mSocketInput = nullptr; |
|
166 mSocketOutput = nullptr; |
|
167 } |
|
168 |
|
169 return NS_OK; |
|
170 } |
|
171 |
|
172 nsresult |
|
173 nsFtpControlConnection::Write(const nsCSubstring& command) |
|
174 { |
|
175 NS_ENSURE_STATE(mSocketOutput); |
|
176 |
|
177 uint32_t len = command.Length(); |
|
178 uint32_t cnt; |
|
179 nsresult rv = mSocketOutput->Write(command.Data(), len, &cnt); |
|
180 |
|
181 if (NS_FAILED(rv)) |
|
182 return rv; |
|
183 |
|
184 if (len != cnt) |
|
185 return NS_ERROR_FAILURE; |
|
186 |
|
187 return NS_OK; |
|
188 } |