1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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 "msgCore.h" // precompiled header...
7
8 #include "nsNntpUrl.h"
9
10 #include "nsString.h"
11 #include "nsNewsUtils.h"
12 #include "nsMsgUtils.h"
13
14 #include "nntpCore.h"
15
16 #include "nsCOMPtr.h"
17 #include "nsMsgDBCID.h"
18 #include "nsMsgNewsCID.h"
19 #include "nsIMsgFolder.h"
20 #include "nsIMsgNewsFolder.h"
21 #include "nsINntpService.h"
22 #include "nsIMsgMessageService.h"
23 #include "nsIMsgAccountManager.h"
24 #include "nsServiceManagerUtils.h"
25
nsNntpUrl()26 nsNntpUrl::nsNntpUrl() {
27 m_newsgroupPost = nullptr;
28 m_newsAction = nsINntpUrl::ActionUnknown;
29 m_addDummyEnvelope = false;
30 m_canonicalLineEnding = false;
31 m_filePath = nullptr;
32 m_getOldMessages = false;
33 m_key = nsMsgKey_None;
34 mOverrideCharset = false;
35 }
36
~nsNntpUrl()37 nsNntpUrl::~nsNntpUrl() {}
38
NS_IMPL_ADDREF_INHERITED(nsNntpUrl,nsMsgMailNewsUrl)39 NS_IMPL_ADDREF_INHERITED(nsNntpUrl, nsMsgMailNewsUrl)
40 NS_IMPL_RELEASE_INHERITED(nsNntpUrl, nsMsgMailNewsUrl)
41
42 NS_INTERFACE_MAP_BEGIN(nsNntpUrl)
43 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINntpUrl)
44 NS_INTERFACE_MAP_ENTRY(nsINntpUrl)
45 NS_INTERFACE_MAP_ENTRY(nsIMsgMessageUrl)
46 NS_INTERFACE_MAP_ENTRY(nsIMsgI18NUrl)
47 NS_INTERFACE_MAP_END_INHERITING(nsMsgMailNewsUrl)
48
49 ////////////////////////////////////////////////////////////////////////////////
50 // Begin nsINntpUrl specific support
51 ////////////////////////////////////////////////////////////////////////////////
52
53 /* News URI parsing explanation:
54 * We support 3 different news URI schemes, essentially boiling down to 8
55 * different formats:
56 * news://host/group
57 * news://host/message
58 * news://host/
59 * news:group
60 * news:message
61 * nntp://host/group
62 * nntp://host/group/key
63 * news-message://host/group#key
64 *
65 * In addition, we use queries on the news URIs with authorities for internal
66 * NNTP processing. The most important one is ?group=group&key=key, for cache
67 * canonicalization.
68 */
69
70 nsresult nsNntpUrl::SetSpecInternal(const nsACString& aSpec) {
71 // For [s]news: URIs, we need to munge the spec if it is no authority, because
72 // the URI parser guesses the wrong thing otherwise
73 nsCString parseSpec(aSpec);
74 int32_t colon = parseSpec.Find(":");
75
76 // Our smallest scheme is 4 characters long, so colon must be at least 4
77 if (colon < 4 || colon + 1 == (int32_t)parseSpec.Length())
78 return NS_ERROR_MALFORMED_URI;
79
80 if (Substring(parseSpec, colon - 4, 4).EqualsLiteral("news") &&
81 parseSpec[colon + 1] != '/') {
82 // To make this parse properly, we add in three slashes, which convinces the
83 // parser that the authority component is empty.
84 parseSpec = Substring(aSpec, 0, colon + 1);
85 parseSpec.AppendLiteral("///");
86 parseSpec += Substring(aSpec, colon + 1);
87 }
88
89 nsresult rv = nsMsgMailNewsUrl::SetSpecInternal(parseSpec);
90 NS_ENSURE_SUCCESS(rv, rv);
91
92 nsAutoCString scheme;
93 rv = GetScheme(scheme);
94 NS_ENSURE_SUCCESS(rv, rv);
95
96 if (scheme.EqualsLiteral("news") || scheme.EqualsLiteral("snews"))
97 rv = ParseNewsURL();
98 else if (scheme.EqualsLiteral("nntp") || scheme.EqualsLiteral("nntps"))
99 rv = ParseNntpURL();
100 else if (scheme.EqualsLiteral("news-message")) {
101 nsAutoCString spec;
102 rv = GetSpec(spec);
103 NS_ENSURE_SUCCESS(rv, rv);
104 rv = nsParseNewsMessageURI(spec, m_group, &m_key);
105 NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
106 } else
107 return NS_ERROR_MALFORMED_URI;
108 NS_ENSURE_SUCCESS(rv, rv);
109
110 rv = DetermineNewsAction();
111 NS_ENSURE_SUCCESS(rv, rv);
112 return rv;
113 }
114
ParseNewsURL()115 nsresult nsNntpUrl::ParseNewsURL() {
116 // The path here is the group/msgid portion
117 nsAutoCString path;
118 nsresult rv = GetFilePath(path);
119 NS_ENSURE_SUCCESS(rv, rv);
120
121 // Drop the potential beginning from the path
122 if (path.Length() && path[0] == '/') path = Substring(path, 1);
123
124 // The presence of an `@' is a sign we have a msgid
125 if (path.Find("@") != -1 || path.Find("%40") != -1) {
126 MsgUnescapeString(path, 0, m_messageID);
127
128 // Set group, key for ?group=foo&key=123 uris
129 nsAutoCString spec;
130 rv = GetSpec(spec);
131 NS_ENSURE_SUCCESS(rv, rv);
132 int32_t groupPos = spec.Find(kNewsURIGroupQuery); // find ?group=
133 int32_t keyPos = spec.Find(kNewsURIKeyQuery); // find &key=
134 if (groupPos != kNotFound && keyPos != kNotFound) {
135 // get group name and message key
136 m_group = Substring(spec, groupPos + kNewsURIGroupQueryLen,
137 keyPos - groupPos - kNewsURIGroupQueryLen);
138 nsCString keyStr(Substring(spec, keyPos + kNewsURIKeyQueryLen));
139 m_key = keyStr.ToInteger(&rv, 10);
140 NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
141 }
142 } else
143 MsgUnescapeString(path, 0, m_group);
144
145 return NS_OK;
146 }
147
ParseNntpURL()148 nsresult nsNntpUrl::ParseNntpURL() {
149 nsAutoCString path;
150 nsresult rv = GetFilePath(path);
151 NS_ENSURE_SUCCESS(rv, rv);
152
153 if (path.Length() > 0 && path[0] == '/') path = Substring(path, 1);
154
155 if (path.IsEmpty()) return NS_ERROR_MALFORMED_URI;
156
157 int32_t slash = path.FindChar('/');
158 if (slash == -1) {
159 m_group = path;
160 m_key = nsMsgKey_None;
161 } else {
162 m_group = Substring(path, 0, slash);
163 nsAutoCString keyStr;
164 keyStr = Substring(path, slash + 1);
165 m_key = keyStr.ToInteger(&rv, 10);
166 NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
167
168 // Keys must be at least one
169 if (m_key == 0) return NS_ERROR_MALFORMED_URI;
170 }
171
172 return NS_OK;
173 }
174
DetermineNewsAction()175 nsresult nsNntpUrl::DetermineNewsAction() {
176 nsAutoCString path;
177 nsresult rv = nsMsgMailNewsUrl::GetPathQueryRef(path);
178 NS_ENSURE_SUCCESS(rv, rv);
179
180 nsAutoCString query;
181 rv = GetQuery(query);
182 NS_ENSURE_SUCCESS(rv, rv);
183
184 if (query.EqualsLiteral("cancel")) {
185 m_newsAction = nsINntpUrl::ActionCancelArticle;
186 return NS_OK;
187 }
188 if (query.EqualsLiteral("list-ids")) {
189 m_newsAction = nsINntpUrl::ActionListIds;
190 return NS_OK;
191 }
192 if (query.EqualsLiteral("newgroups")) {
193 m_newsAction = nsINntpUrl::ActionListNewGroups;
194 return NS_OK;
195 }
196 if (StringBeginsWith(query, "search"_ns)) {
197 m_newsAction = nsINntpUrl::ActionSearch;
198 return NS_OK;
199 }
200 if (StringBeginsWith(query, "part="_ns) || query.Find("&part=") > 0) {
201 // news://news.mozilla.org:119/3B98D201.3020100%40cs.com?part=1
202 // news://news.mozilla.org:119/b58dme%24aia2%40ripley.netscape.com?header=print&part=1.2&type=image/jpeg&filename=Pole.jpg
203 m_newsAction = nsINntpUrl::ActionFetchPart;
204 return NS_OK;
205 }
206
207 if (!m_messageID.IsEmpty() || m_key != nsMsgKey_None) {
208 m_newsAction = nsINntpUrl::ActionFetchArticle;
209 return NS_OK;
210 }
211
212 if (m_group.Find("*") >= 0) {
213 // If the group is a wildmat, list groups instead of grabbing a group.
214 m_newsAction = nsINntpUrl::ActionListGroups;
215 return NS_OK;
216 }
217 if (!m_group.IsEmpty()) {
218 m_newsAction = nsINntpUrl::ActionGetNewNews;
219 return NS_OK;
220 }
221
222 // At this point, we have a URI that contains neither a query, a group, nor a
223 // message ID. Ergo, we don't know what it is.
224 m_newsAction = nsINntpUrl::ActionUnknown;
225 return NS_OK;
226 }
227
SetGetOldMessages(bool aGetOldMessages)228 NS_IMETHODIMP nsNntpUrl::SetGetOldMessages(bool aGetOldMessages) {
229 m_getOldMessages = aGetOldMessages;
230 return NS_OK;
231 }
232
GetGetOldMessages(bool * aGetOldMessages)233 NS_IMETHODIMP nsNntpUrl::GetGetOldMessages(bool* aGetOldMessages) {
234 NS_ENSURE_ARG(aGetOldMessages);
235 *aGetOldMessages = m_getOldMessages;
236 return NS_OK;
237 }
238
GetNewsAction(nsNewsAction * aNewsAction)239 NS_IMETHODIMP nsNntpUrl::GetNewsAction(nsNewsAction* aNewsAction) {
240 if (aNewsAction) *aNewsAction = m_newsAction;
241 return NS_OK;
242 }
243
SetNewsAction(nsNewsAction aNewsAction)244 NS_IMETHODIMP nsNntpUrl::SetNewsAction(nsNewsAction aNewsAction) {
245 m_newsAction = aNewsAction;
246 return NS_OK;
247 }
248
GetGroup(nsACString & group)249 NS_IMETHODIMP nsNntpUrl::GetGroup(nsACString& group) {
250 group = m_group;
251 return NS_OK;
252 }
253
GetMessageID(nsACString & messageID)254 NS_IMETHODIMP nsNntpUrl::GetMessageID(nsACString& messageID) {
255 messageID = m_messageID;
256 return NS_OK;
257 }
258
GetKey(nsMsgKey * key)259 NS_IMETHODIMP nsNntpUrl::GetKey(nsMsgKey* key) {
260 NS_ENSURE_ARG_POINTER(key);
261 *key = m_key;
262 return NS_OK;
263 }
264
GetCharset(nsACString & charset)265 NS_IMETHODIMP nsNntpUrl::GetCharset(nsACString& charset) {
266 nsCOMPtr<nsIMsgIncomingServer> server;
267 nsresult rv = GetServer(getter_AddRefs(server));
268 NS_ENSURE_SUCCESS(rv, rv);
269 nsCOMPtr<nsINntpIncomingServer> nserver(do_QueryInterface(server));
270 NS_ENSURE_TRUE(nserver, NS_ERROR_NULL_POINTER);
271 return nserver->GetCharset(charset);
272 }
273
GetNormalizedSpec(nsACString & aPrincipalSpec)274 NS_IMETHODIMP nsNntpUrl::GetNormalizedSpec(nsACString& aPrincipalSpec) {
275 return NS_ERROR_NOT_IMPLEMENTED;
276 }
277
SetUri(const nsACString & aURI)278 NS_IMETHODIMP nsNntpUrl::SetUri(const nsACString& aURI) {
279 mURI = aURI;
280 return NS_OK;
281 }
282
283 // from nsIMsgMessageUrl
GetUri(nsACString & aURI)284 NS_IMETHODIMP nsNntpUrl::GetUri(nsACString& aURI) {
285 nsresult rv = NS_OK;
286
287 // if we have been given a uri to associate with this url, then use it
288 // otherwise try to reconstruct a URI on the fly....
289 if (mURI.IsEmpty()) {
290 nsAutoCString spec;
291 rv = GetSpec(spec);
292 NS_ENSURE_SUCCESS(rv, rv);
293 mURI = spec;
294 }
295
296 aURI = mURI;
297 return rv;
298 }
299
NS_IMPL_GETSET(nsNntpUrl,AddDummyEnvelope,bool,m_addDummyEnvelope)300 NS_IMPL_GETSET(nsNntpUrl, AddDummyEnvelope, bool, m_addDummyEnvelope)
301 NS_IMPL_GETSET(nsNntpUrl, CanonicalLineEnding, bool, m_canonicalLineEnding)
302
303 NS_IMETHODIMP nsNntpUrl::SetMessageFile(nsIFile* aFile) {
304 m_messageFile = aFile;
305 return NS_OK;
306 }
307
GetMessageFile(nsIFile ** aFile)308 NS_IMETHODIMP nsNntpUrl::GetMessageFile(nsIFile** aFile) {
309 if (aFile) NS_IF_ADDREF(*aFile = m_messageFile);
310 return NS_OK;
311 }
312
313 ////////////////////////////////////////////////////////////////////////////////
314 // End nsINntpUrl specific support
315 ////////////////////////////////////////////////////////////////////////////////
316
SetMessageToPost(nsINNTPNewsgroupPost * post)317 nsresult nsNntpUrl::SetMessageToPost(nsINNTPNewsgroupPost* post) {
318 m_newsgroupPost = post;
319 if (post) SetNewsAction(nsINntpUrl::ActionPostArticle);
320 return NS_OK;
321 }
322
GetMessageToPost(nsINNTPNewsgroupPost ** aPost)323 nsresult nsNntpUrl::GetMessageToPost(nsINNTPNewsgroupPost** aPost) {
324 NS_ENSURE_ARG_POINTER(aPost);
325 NS_IF_ADDREF(*aPost = m_newsgroupPost);
326 return NS_OK;
327 }
328
SetMessageHeader(nsIMsgDBHdr * aMsgHdr)329 NS_IMETHODIMP nsNntpUrl::SetMessageHeader(nsIMsgDBHdr* aMsgHdr) {
330 return NS_ERROR_NOT_IMPLEMENTED;
331 }
332
GetMessageHeader(nsIMsgDBHdr ** aMsgHdr)333 NS_IMETHODIMP nsNntpUrl::GetMessageHeader(nsIMsgDBHdr** aMsgHdr) {
334 nsresult rv;
335
336 nsCOMPtr<nsINntpService> nntpService =
337 do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
338 NS_ENSURE_SUCCESS(rv, rv);
339
340 nsCOMPtr<nsIMsgMessageService> msgService =
341 do_QueryInterface(nntpService, &rv);
342 NS_ENSURE_SUCCESS(rv, rv);
343
344 nsAutoCString spec(mOriginalSpec);
345 if (spec.IsEmpty()) {
346 // Handle the case where necko directly runs an internal news:// URL,
347 // one that looks like news://host/message-id?group=mozilla.announce&key=15
348 // Other sorts of URLs -- e.g. news://host/message-id -- will not succeed.
349 rv = GetSpec(spec);
350 NS_ENSURE_SUCCESS(rv, rv);
351 }
352
353 return msgService->MessageURIToMsgHdr(spec, aMsgHdr);
354 }
355
IsUrlType(uint32_t type,bool * isType)356 NS_IMETHODIMP nsNntpUrl::IsUrlType(uint32_t type, bool* isType) {
357 NS_ENSURE_ARG(isType);
358
359 switch (type) {
360 case nsIMsgMailNewsUrl::eDisplay:
361 *isType = (m_newsAction == nsINntpUrl::ActionFetchArticle);
362 break;
363 default:
364 *isType = false;
365 };
366
367 return NS_OK;
368 }
369
370 NS_IMETHODIMP
GetOriginalSpec(nsACString & aSpec)371 nsNntpUrl::GetOriginalSpec(nsACString& aSpec) {
372 aSpec = mOriginalSpec;
373 return NS_OK;
374 }
375
376 NS_IMETHODIMP
SetOriginalSpec(const nsACString & aSpec)377 nsNntpUrl::SetOriginalSpec(const nsACString& aSpec) {
378 mOriginalSpec = aSpec;
379 return NS_OK;
380 }
381
382 NS_IMETHODIMP
GetServer(nsIMsgIncomingServer ** aServer)383 nsNntpUrl::GetServer(nsIMsgIncomingServer** aServer) {
384 NS_ENSURE_ARG_POINTER(aServer);
385
386 nsresult rv;
387 nsAutoCString scheme, user, host;
388
389 GetScheme(scheme);
390 GetUsername(user);
391 GetHost(host);
392
393 // No authority -> no server
394 if (host.IsEmpty()) {
395 *aServer = nullptr;
396 return NS_OK;
397 }
398
399 // Looking up the server...
400 // news-message is used purely internally, so it can never refer to the real
401 // attribute. nntp is never used internally, so it probably refers to the real
402 // one. news is used both internally and externally, so it could refer to
403 // either one. We'll assume it's an internal one first, though.
404 bool isNews = scheme.EqualsLiteral("news") || scheme.EqualsLiteral("snews");
405 bool isNntp = scheme.EqualsLiteral("nntp") || scheme.EqualsLiteral("nntps");
406
407 bool tryReal = isNntp;
408
409 nsCOMPtr<nsIMsgAccountManager> accountManager =
410 do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
411 NS_ENSURE_SUCCESS(rv, rv);
412
413 // Ignoring return results: it is perfectly acceptable for the server to not
414 // exist, but FindServer (and not FindRealServer) throws NS_ERROR_UNEXPECTED
415 // in this case.
416 *aServer = nullptr;
417 if (tryReal)
418 accountManager->FindRealServer(user, host, "nntp"_ns, 0, aServer);
419 else
420 accountManager->FindServer(user, host, "nntp"_ns, aServer);
421 if (!*aServer && (isNews || isNntp)) {
422 // Didn't find it, try the other option
423 if (tryReal)
424 accountManager->FindServer(user, host, "nntp"_ns, aServer);
425 else
426 accountManager->FindRealServer(user, host, "nntp"_ns, 0, aServer);
427 }
428 return NS_OK;
429 }
430
GetFolder(nsIMsgFolder ** msgFolder)431 NS_IMETHODIMP nsNntpUrl::GetFolder(nsIMsgFolder** msgFolder) {
432 NS_ENSURE_ARG_POINTER(msgFolder);
433
434 nsresult rv;
435
436 nsCOMPtr<nsIMsgIncomingServer> server;
437 rv = GetServer(getter_AddRefs(server));
438 NS_ENSURE_SUCCESS(rv, rv);
439
440 // Need a server and a group to get the folder
441 if (!server || m_group.IsEmpty()) {
442 *msgFolder = nullptr;
443 return NS_OK;
444 }
445
446 // Find the group on the server
447 nsCOMPtr<nsINntpIncomingServer> nntpServer = do_QueryInterface(server, &rv);
448 NS_ENSURE_SUCCESS(rv, rv);
449
450 bool hasGroup = false;
451 rv = nntpServer->ContainsNewsgroup(m_group, &hasGroup);
452 NS_ENSURE_SUCCESS(rv, rv);
453
454 if (!hasGroup) {
455 *msgFolder = nullptr;
456 return NS_OK;
457 }
458
459 nsCOMPtr<nsIMsgNewsFolder> newsFolder;
460 rv = nntpServer->FindGroup(m_group, getter_AddRefs(newsFolder));
461 NS_ENSURE_SUCCESS(rv, rv);
462
463 return newsFolder->QueryInterface(NS_GET_IID(nsIMsgFolder),
464 (void**)msgFolder);
465 }
466
GetOverRideCharset(bool * aOverride)467 NS_IMETHODIMP nsNntpUrl::GetOverRideCharset(bool* aOverride) {
468 *aOverride = mOverrideCharset;
469 return NS_OK;
470 }
471
SetOverRideCharset(bool aOverride)472 NS_IMETHODIMP nsNntpUrl::SetOverRideCharset(bool aOverride) {
473 mOverrideCharset = aOverride;
474 return NS_OK;
475 }
476
Clone(nsIURI ** _retval)477 nsresult nsNntpUrl::Clone(nsIURI** _retval) {
478 nsresult rv;
479 rv = nsMsgMailNewsUrl::Clone(_retval);
480 NS_ENSURE_SUCCESS(rv, rv);
481
482 nsCOMPtr<nsIMsgMessageUrl> newsurl = do_QueryInterface(*_retval, &rv);
483 NS_ENSURE_SUCCESS(rv, rv);
484
485 return newsurl->SetUri(mURI);
486 }
487