Does email parsing follow http links?

Hello,

We are using GroupDocs parser and we have noticed that our application makes attempts to make http calls to random websites. We haven’t managed to pinpoint what is making those call but we are wondering if it could come from GroupDocs parser, in particular when processing emails.

Basically, can you tell me is there is any cirsumstance in which GroupDocs parser would make a call to a link found in an msg file? (be it a clickable link or an image hosted externally for example)

Thank you

Hello, @woutedaeu !

Thanks for your patience — I wanted to verify this against the actual behaviour and measure it before answering, rather than just reassure you.

Short answer: yes. Under specific circumstances GroupDocs.Parser does issue HTTP requests to URLs found inside a document, including MSG and EML. It does not follow clickable links — what it fetches are externally hosted resources the message body refers to.

What is and isn’t requested

  • Clickable hyperlinks — never. GetHyperlinks() returns the target and anchor text as strings; it never resolves or visits them. A URL in body text is just text to GetText().
  • Externally hosted images and stylesheets — yes. <img src="http://…"> and <link href="http://…"> in an HTML body are downloaded while the body is laid out.
  • Attachments — never. Read from bytes already in the file.

That fits your “random websites” observation: marketing and phishing mail routinely embeds remote images and tracking pixels, each on a different domain.

Which calls trigger it. For MSG/EML the request is not made by image extraction — it’s made by these:

Method Outbound request
GetText() No
GetFormattedText() Yes
GetStructure() Yes
GetImages(), GetContainer(), GetMetadata(), GetDocumentInfo() No

So if your pipeline calls GetFormattedText() or GetStructure() on incoming mail, that’s almost certainly the source. (The request is issued on an internal worker thread, not the calling thread — which is why it won’t show under your code in a stack trace and looks like it comes from nowhere. Correlate by timing, or block egress and watch it stop.)

About the workaround — being straight with you. ParserSettings.ExternalResourceHandler is the documented control, but in the current version it’s honoured for image extraction from HTML and MHTML only. On the email paths, OnLoading is never invoked and the request is still sent. We consider that a gap, not intended behaviour, and I’ll follow up here once I have something concrete. For now your reliable options are:

  1. Use GetText() where plain text is enough — network-free for MSG/EML.
  2. Block egress for the parsing component (container, network namespace, or subnet with no outbound route). This is the only control that covers every path.

Beyond email, the same mechanism applies elsewhere and the affected method varies by format: HTML/MHTML (GetText, GetFormattedText, GetStructure, GetImages can all fetch — and on MHTML even GetDocumentInfo() does); linked images in Word documents via GetImages(); RTF INCLUDEPICTURE fields via GetText(). Spreadsheet/presentation/PDF aren’t characterized yet, so treat that as confirmed cases rather than a safe list.

We’ve published the full details — a measured per-format/per-method table, the handler limits, and air-gapped guidance:

And a runnable example that reproduces exactly what you’re seeing: External Resources In Email Messages

To confirm it’s us: capture the outbound URLs and check whether they appear verbatim in the messages you process. If they do — and blocking egress still gives you the output you need — that’s the cause and a complete fix. If traffic persists, something else in your stack is responsible and we’ll gladly help you narrow it down.

For completeness: apart from external resources, Parser reaches the network only where your own code asks — the Parser(Uri), Parser(EmailConnection) and Parser(DbConnection) overloads, a metered license (usage volume only, never document content), and an HTTP license-file download if configured. None is triggered by document content, and your documents themselves are never transmitted to GroupDocs in any configuration.

Thanks again for reporting this — it prompted a review that was overdue.

If you are able to share your particular code snippet and document, that will help us to clarify the entire situation.

Kind regards!

Thanks Yuriy for the very detailed answer.

The GetText method is already what we are using, so that’s very strange.
I least now we know why the ResourceHandler isn’t being called. I will experiment with the code that you provided and see if I can intercept the calls in other ways. (We’re on Java so it’ll be a bit different.)
(Unfortunately I cannot provide you with a sample document, mostly because it may contain confidential data.)

Hi @woutedaeu !

We are going to align this behavior in the GroupDocs.Parser to provide ability to control any external resource loading. Hopefully we will be able to deliver this early next month with Java version.

May I ask you to share code-snippet you use
Is it just GetText() for locally located document or loading on stream?

We are loading from stream.
Here is our code (simplified a bit for clarity)

    public void extractText(InputStream fileToParse, Writer writer) throws ParsingException {
        LoadOptions loadOptions = new LoadOptions(FileFormat.Email);
        try (Parser parser = new Parser(fileToParse, loadOptions)) {
            parse(parser, writer);
        } catch (IOException e) {
            throw new ParsingException("Unable to parse file", e);
        }
    }

    public void parse(Parser parser, Writer writer) throws IOException {
        Features features = parser.getFeatures();
        if (features.isText()) {
            try (TextReader reader = parser.getText()) {
                String line;
                while ((line = reader.readLine()) != null) {
                    writer.write(line);
                    writer.write('\n');
                }
            }
        }
    }

Thank you again

Hi @woutedaeu!

Thank you for shared code-snippet.
Once we adjust and publish a new version of GroupDocs.Parser for Java - I will post an update here to notify you.
Due to summer vacations, it will be possible in early September dates.

Hello, @woutedaeu !

Thanks for the code snippet — that was enough to reproduce your exact call path on Java. I re-ran the whole measurement against the Java build rather than assuming it matches .NET, and here is what came out.

Short answer: your snippet, as written, does not issue any outbound request. I could not make getText() on MSG/EML touch the network in any message shape I was able to construct. So on the evidence, the traffic you are seeing is coming from somewhere else — and I have a concrete suspect below.

What I measured

GroupDocs.Parser for Java 26.5 (Aspose.Email 24.9, Aspose.Words 23.6). Every message body pointed its <img src>, <link href> and INCLUDEPICTURE at a local HTTP server, and I counted the requests that actually arrived. Loading was done exactly as in your code — from a stream, with new LoadOptions(FileFormat.Email).

Document getText() getFormattedText() getStructure() getImages()
MSG, plain-text + HTML body no requests CSS + image CSS + image no requests
MSG, HTML body only no requests CSS + image CSS + image no requests
MSG with no plain-body property at all no requests
MSG, RTF body with INCLUDEPICTURE no requests
EML, hand-written, text/html only no requests CSS + image CSS + image no requests
S/MIME-signed MSG (cert carrying CRL + OCSP + caIssuers URLs) no requests
plain HTML file CSS + image CSS + image CSS + image CSS + image

So the per-method table from my earlier post holds for Java as well: on email, GetFormattedText/getFormattedText and GetStructure/getStructure fetch external resources; getText does not.

Why getText() stays offline on email — and the one caveat

Internally the email parser does this:

if (msg.getBody() != null) return msg.getBody();   // plain-text body, no network
switch (msg.getBodyType()) {
    case Html: return WordProcessingUtils.getText(...);  // Aspose.Words
    case Rtf:  return WordProcessingUtils.getText(...);  // Aspose.Words
}

Those two fallback branches are network-active — I called them directly and Aspose.Words downloaded the stylesheet, the image, and the RTF INCLUDEPICTURE target even in plain-text mode. But I could not reach them from a real file: Aspose.Email synthesises a plain-text body from the HTML or RTF part itself, offline, so getBody() came back non-null even for a .msg that physically has no plain-body stream (I checked the compound-file streams byte-wise). In practice those branches are dead code. I am flagging them because they are the only way getText() on email could ever go out, not because I saw them fire.

One honest caveat: these runs were unlicensed (evaluation mode), which watermarks the body. That does not affect the request counts — they were measured on the wire — but I cannot fully rule out a rare licensed case where getBody() returns null.

Things I ruled out

  • Signature handling. The Java parser loads messages with removeSignature(true), so I tested a signed .msg whose certificate carried CRL, OCSP and caIssuers URLs pointing at my server. Nothing was fetched — no revocation or chain-building traffic.
  • Format misrouting. With new LoadOptions(FileFormat.Email) only the email detector runs; anything else fails with UnsupportedDocumentFormatException rather than silently falling through to the HTML parser. PST/OST would be accepted, but they do not support the text feature, so your features.isText() guard skips them.
  • Anything else in the library. The only outbound HTTP in Parser’s own Java code is the metered-licensing client (HTTPS to the GroupDocs API, usage counts only). Everything else is an explicit Parser(URL) / Parser(EmailConnection) / Parser(Connection) you would have written yourself.

The likely culprit: HTML content elsewhere in your pipeline

Note the last row of the table. On Java, getText() on an HTML document downloads external stylesheets and images — same as .NET. This is the one place where the method you already use is network-active.

That matters if anywhere else in your application:

  • attachments pulled out of messages (getContainer()) are fed back into Parser, and some of them are .html / .mht;
  • files arrive with an unknown or guessed type and are parsed without a fixed FileFormat, so detection lands on HTML or MHTML;
  • HTML bodies are saved out and re-parsed as documents.

Any of those would produce exactly what you describe — requests to a different domain per message, from a worker thread, with nothing suspicious in your own stack trace.

Good news on the handler

You noticed the ResourceHandler was never invoked. On the email paths that is correct and it is a gap on our side — the handler is not passed through to the layout engine there. But on the HTML and MHTML paths it does work in Java, via ParserSettings(ExternalResourceHandler). So if the source turns out to be HTML content, you have a real interception point, not just an egress block:

ParserSettings settings = new ParserSettings(new ExternalResourceHandler() {
    @Override public void onLoading(ExternalResourceLoadingArgs args) {
        // args.getUri() — log it, then hand back empty data to suppress the fetch
        args.setData(new byte[0]);
    }
});
try (Parser parser = new Parser(fileToParse, loadOptions, settings)) { ... }

Even if you end up blocking egress anyway, running this once with logging only is the fastest way to find out which call site is responsible — every URL the library is about to request will pass through onLoading on those paths.

What would settle it

  1. Capture the outbound URLs and check whether they appear verbatim in the messages you process.
  2. Grep for every other place your code constructs a Parser — especially attachment handling and any path without an explicit FileFormat.
  3. Drop in the handler above on those paths and log args.getUri().

If the URLs do come from the message bodies but never appear in onLoading, send me that call site and I will dig further. And thanks again — this thread has already produced one fix worth making on our side.

Kind regards!