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