Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "msgCore.h" // precompiled header...
#include "nsNntpUrl.h"
#include "nsString.h"
#include "nsNewsUtils.h"
#include "nsMsgUtils.h"
#include "nntpCore.h"
#include "nsCOMPtr.h"
#include "nsIMsgFolder.h"
#include "nsIMsgNewsFolder.h"
#include "nsINntpService.h"
#include "nsIMsgMessageService.h"
#include "nsIMsgAccountManager.h"
#include "nsServiceManagerUtils.h"
nsNntpUrl::nsNntpUrl() {
m_newsgroupPost = nullptr;
m_newsAction = nsINntpUrl::ActionUnknown;
m_addDummyEnvelope = false;
m_canonicalLineEnding = false;
m_filePath = nullptr;
m_getOldMessages = false;
m_key = nsMsgKey_None;
mAutodetectCharset = false;
}
nsNntpUrl::~nsNntpUrl() {}
NS_IMPL_ADDREF_INHERITED(nsNntpUrl, nsMsgMailNewsUrl)
NS_IMPL_RELEASE_INHERITED(nsNntpUrl, nsMsgMailNewsUrl)
NS_INTERFACE_MAP_BEGIN(nsNntpUrl)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINntpUrl)
NS_INTERFACE_MAP_ENTRY(nsINntpUrl)
NS_INTERFACE_MAP_ENTRY(nsIMsgMessageUrl)
NS_INTERFACE_MAP_ENTRY(nsIMsgI18NUrl)
NS_INTERFACE_MAP_END_INHERITING(nsMsgMailNewsUrl)
////////////////////////////////////////////////////////////////////////////////
// Begin nsINntpUrl specific support
////////////////////////////////////////////////////////////////////////////////
/* News URI parsing explanation:
* We support 3 different news URI schemes, essentially boiling down to 8
* different formats:
* news:group
* news:message
*
* In addition, we use queries on the news URIs with authorities for internal
* NNTP processing. The most important one is ?group=group&key=key, for cache
* canonicalization.
*/
nsresult nsNntpUrl::SetSpecInternal(const nsACString& aSpec) {
// For [s]news: URIs, we need to munge the spec if it is no authority, because
// the URI parser guesses the wrong thing otherwise
nsCString parseSpec(aSpec);
int32_t colon = parseSpec.Find(":");
// Our smallest scheme is 4 characters long, so colon must be at least 4
if (colon < 4 || colon + 1 == (int32_t)parseSpec.Length())
return NS_ERROR_MALFORMED_URI;
if (Substring(parseSpec, colon - 4, 4).EqualsLiteral("news") &&
parseSpec[colon + 1] != '/') {
// To make this parse properly, we add in three slashes, which convinces the
// parser that the authority component is empty.
parseSpec = Substring(aSpec, 0, colon + 1);
parseSpec.AppendLiteral("///");
parseSpec += Substring(aSpec, colon + 1);
}
nsresult rv = nsMsgMailNewsUrl::SetSpecInternal(parseSpec);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString scheme;
rv = GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
if (scheme.EqualsLiteral("news") || scheme.EqualsLiteral("snews"))
rv = ParseNewsURL();
else if (scheme.EqualsLiteral("nntp") || scheme.EqualsLiteral("nntps"))
rv = ParseNntpURL();
else if (scheme.EqualsLiteral("news-message")) {
nsAutoCString spec;
rv = GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
rv = nsParseNewsMessageURI(spec, m_group, &m_key);
NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
} else
return NS_ERROR_MALFORMED_URI;
NS_ENSURE_SUCCESS(rv, rv);
rv = DetermineNewsAction();
NS_ENSURE_SUCCESS(rv, rv);
return rv;
}
nsresult nsNntpUrl::ParseNewsURL() {
// The path here is the group/msgid portion
nsAutoCString path;
nsresult rv = GetFilePath(path);
NS_ENSURE_SUCCESS(rv, rv);
// Drop the potential beginning from the path
if (path.Length() && path[0] == '/') path = Substring(path, 1);
// The presence of an `@' is a sign we have a msgid
if (path.Find("@") != -1 || path.Find("%40") != -1) {
MsgUnescapeString(path, 0, m_messageID);
// Set group, key for ?group=foo&key=123 uris
nsAutoCString spec;
rv = GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
int32_t groupPos = spec.Find(kNewsURIGroupQuery); // find ?group=
int32_t keyPos = spec.Find(kNewsURIKeyQuery); // find &key=
if (groupPos != kNotFound && keyPos != kNotFound) {
// get group name and message key
m_group = Substring(spec, groupPos + kNewsURIGroupQueryLen,
keyPos - groupPos - kNewsURIGroupQueryLen);
nsCString keyStr(Substring(spec, keyPos + kNewsURIKeyQueryLen));
m_key = keyStr.ToInteger(&rv, 10);
NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
}
} else
MsgUnescapeString(path, 0, m_group);
return NS_OK;
}
nsresult nsNntpUrl::ParseNntpURL() {
nsAutoCString path;
nsresult rv = GetFilePath(path);
NS_ENSURE_SUCCESS(rv, rv);
if (path.Length() > 0 && path[0] == '/') path = Substring(path, 1);
if (path.IsEmpty()) return NS_ERROR_MALFORMED_URI;
int32_t slash = path.FindChar('/');
if (slash == -1) {
m_group = path;
m_key = nsMsgKey_None;
} else {
m_group = Substring(path, 0, slash);
nsAutoCString keyStr;
keyStr = Substring(path, slash + 1);
m_key = keyStr.ToInteger(&rv, 10);
NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
// Keys must be at least one
if (m_key == 0) return NS_ERROR_MALFORMED_URI;
}
return NS_OK;
}
nsresult nsNntpUrl::DetermineNewsAction() {
nsAutoCString path;
nsresult rv = nsMsgMailNewsUrl::GetPathQueryRef(path);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString query;
rv = GetQuery(query);
NS_ENSURE_SUCCESS(rv, rv);
if (query.EqualsLiteral("cancel")) {
m_newsAction = nsINntpUrl::ActionCancelArticle;
return NS_OK;
}
if (query.EqualsLiteral("list-ids")) {
m_newsAction = nsINntpUrl::ActionListIds;
return NS_OK;
}
if (query.EqualsLiteral("newgroups")) {
m_newsAction = nsINntpUrl::ActionListNewGroups;
return NS_OK;
}
if (StringBeginsWith(query, "search"_ns)) {
m_newsAction = nsINntpUrl::ActionSearch;
return NS_OK;
}
if (StringBeginsWith(query, "part="_ns) || query.Find("&part=") > 0) {
m_newsAction = nsINntpUrl::ActionFetchPart;
return NS_OK;
}
if (!m_messageID.IsEmpty() || m_key != nsMsgKey_None) {
m_newsAction = nsINntpUrl::ActionFetchArticle;
return NS_OK;
}
if (m_group.Find("*") >= 0) {
// If the group is a wildmat, list groups instead of grabbing a group.
m_newsAction = nsINntpUrl::ActionListGroups;
return NS_OK;
}
if (!m_group.IsEmpty()) {
m_newsAction = nsINntpUrl::ActionGetNewNews;
return NS_OK;
}
// At this point, we have a URI that contains neither a query, a group, nor a
// message ID. Ergo, we don't know what it is.
m_newsAction = nsINntpUrl::ActionUnknown;
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::SetGetOldMessages(bool aGetOldMessages) {
m_getOldMessages = aGetOldMessages;
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::GetGetOldMessages(bool* aGetOldMessages) {
NS_ENSURE_ARG(aGetOldMessages);
*aGetOldMessages = m_getOldMessages;
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::GetNewsAction(nsNewsAction* aNewsAction) {
if (aNewsAction) *aNewsAction = m_newsAction;
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::SetNewsAction(nsNewsAction aNewsAction) {
m_newsAction = aNewsAction;
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::GetGroup(nsACString& group) {
group = m_group;
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::GetMessageID(nsACString& messageID) {
messageID = m_messageID;
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::GetKey(nsMsgKey* key) {
NS_ENSURE_ARG_POINTER(key);
*key = m_key;
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::GetCharset(nsACString& charset) {
nsCOMPtr<nsIMsgIncomingServer> server;
nsresult rv = GetServer(getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsINntpIncomingServer> nserver(do_QueryInterface(server));
NS_ENSURE_TRUE(nserver, NS_ERROR_NULL_POINTER);
return nserver->GetCharset(charset);
}
NS_IMETHODIMP nsNntpUrl::GetNormalizedSpec(nsACString& aPrincipalSpec) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP nsNntpUrl::SetUri(const nsACString& aURI) {
mURI = aURI;
return NS_OK;
}
// from nsIMsgMessageUrl
NS_IMETHODIMP nsNntpUrl::GetUri(nsACString& aURI) {
nsresult rv = NS_OK;
// if we have been given a uri to associate with this url, then use it
// otherwise try to reconstruct a URI on the fly....
if (mURI.IsEmpty()) {
nsAutoCString spec;
rv = GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
mURI = spec;
}
aURI = mURI;
return rv;
}
NS_IMPL_GETSET(nsNntpUrl, AddDummyEnvelope, bool, m_addDummyEnvelope)
NS_IMPL_GETSET(nsNntpUrl, CanonicalLineEnding, bool, m_canonicalLineEnding)
NS_IMETHODIMP nsNntpUrl::SetMessageFile(nsIFile* aFile) {
m_messageFile = aFile;
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::GetMessageFile(nsIFile** aFile) {
if (aFile) NS_IF_ADDREF(*aFile = m_messageFile);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// End nsINntpUrl specific support
////////////////////////////////////////////////////////////////////////////////
nsresult nsNntpUrl::SetMessageToPost(nsINNTPNewsgroupPost* post) {
m_newsgroupPost = post;
if (post) SetNewsAction(nsINntpUrl::ActionPostArticle);
return NS_OK;
}
nsresult nsNntpUrl::GetMessageToPost(nsINNTPNewsgroupPost** aPost) {
NS_ENSURE_ARG_POINTER(aPost);
NS_IF_ADDREF(*aPost = m_newsgroupPost);
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::GetMessageHeader(nsIMsgDBHdr** aMsgHdr) {
nsresult rv;
nsCOMPtr<nsIMsgMessageService> msgService =
do_GetService("@mozilla.org/messenger/messageservice;1?type=news", &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString spec(mOriginalSpec);
if (spec.IsEmpty()) {
// Handle the case where necko directly runs an internal news:// URL,
// Other sorts of URLs -- e.g. news://host/message-id -- will not succeed.
rv = GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
}
return msgService->MessageURIToMsgHdr(spec, aMsgHdr);
}
NS_IMETHODIMP nsNntpUrl::IsUrlType(uint32_t type, bool* isType) {
NS_ENSURE_ARG(isType);
switch (type) {
case nsIMsgMailNewsUrl::eDisplay:
*isType = (m_newsAction == nsINntpUrl::ActionFetchArticle);
break;
default:
*isType = false;
};
return NS_OK;
}
NS_IMETHODIMP
nsNntpUrl::GetOriginalSpec(nsACString& aSpec) {
aSpec = mOriginalSpec;
return NS_OK;
}
NS_IMETHODIMP
nsNntpUrl::SetOriginalSpec(const nsACString& aSpec) {
mOriginalSpec = aSpec;
return NS_OK;
}
NS_IMETHODIMP
nsNntpUrl::GetServer(nsIMsgIncomingServer** aServer) {
NS_ENSURE_ARG_POINTER(aServer);
nsresult rv;
nsAutoCString scheme, user, host;
GetScheme(scheme);
GetUsername(user);
GetHost(host);
// No authority -> no server
if (host.IsEmpty()) {
*aServer = nullptr;
return NS_OK;
}
nsCOMPtr<nsIMsgAccountManager> accountManager =
do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
*aServer = nullptr;
accountManager->FindServer(user, host, "nntp"_ns, 0, aServer);
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::GetFolder(nsIMsgFolder** msgFolder) {
NS_ENSURE_ARG_POINTER(msgFolder);
nsresult rv;
nsCOMPtr<nsIMsgIncomingServer> server;
rv = GetServer(getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv, rv);
// Need a server and a group to get the folder
if (!server || m_group.IsEmpty()) {
*msgFolder = nullptr;
return NS_OK;
}
// Find the group on the server
nsCOMPtr<nsINntpIncomingServer> nntpServer = do_QueryInterface(server, &rv);
NS_ENSURE_SUCCESS(rv, rv);
bool hasGroup = false;
rv = nntpServer->ContainsNewsgroup(m_group, &hasGroup);
NS_ENSURE_SUCCESS(rv, rv);
if (!hasGroup) {
*msgFolder = nullptr;
return NS_OK;
}
nsCOMPtr<nsIMsgNewsFolder> newsFolder;
rv = nntpServer->FindGroup(m_group, getter_AddRefs(newsFolder));
NS_ENSURE_SUCCESS(rv, rv);
return newsFolder->QueryInterface(NS_GET_IID(nsIMsgFolder),
(void**)msgFolder);
}
NS_IMETHODIMP nsNntpUrl::GetAutodetectCharset(bool* aAutodetectCharset) {
*aAutodetectCharset = mAutodetectCharset;
return NS_OK;
}
NS_IMETHODIMP nsNntpUrl::SetAutodetectCharset(bool aAutodetectCharset) {
mAutodetectCharset = aAutodetectCharset;
return NS_OK;
}
nsresult nsNntpUrl::Clone(nsIURI** _retval) {
nsresult rv;
rv = nsMsgMailNewsUrl::Clone(_retval);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgMessageUrl> newsurl = do_QueryInterface(*_retval, &rv);
NS_ENSURE_SUCCESS(rv, rv);
return newsurl->SetUri(mURI);
}
nsresult nsNntpUrl::NewURI(const nsACString& aSpec, nsIURI* aBaseURI,
nsIURI** _retval) {
nsresult rv;
nsCOMPtr<nsIMsgMailNewsUrl> nntpUri =
do_CreateInstance("@mozilla.org/messenger/nntpurl;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (aBaseURI) {
nsAutoCString newSpec;
aBaseURI->Resolve(aSpec, newSpec);
rv = nntpUri->SetSpecInternal(newSpec);
// XXX Consider: rv = NS_MutateURI(new
// nsNntpUrl::Mutator()).SetSpec(newSpec).Finalize(nntpUri);
} else {
rv = nntpUri->SetSpecInternal(aSpec);
}
NS_ENSURE_SUCCESS(rv, rv);
nntpUri.forget(_retval);
return NS_OK;
}