shashank created CAMEL-25575:
--------------------------------

             Summary: camel-dropbox - search returns only the first page of the 
matches (100 by default) although its javadoc promises all files found; the 
rest is dropped silently (get of a folder: first page of the listing)
                 Key: CAMEL-25575
                 URL: https://issues.apache.org/jira/browse/CAMEL-25575
             Project: Camel
          Issue Type: Bug
          Components: camel-dropbox
            Reporter: shashank
            Assignee: shashank


*{{search}}* is the main case. {{DropboxAPIFacade.search}} (lines 245-259 at 
main c3829069cb72), used by the {{dropbox:search}} producer and consumer:
{code:java}
SearchV2Result listing = client.files().searchV2Builder(query)
        .withOptions(SearchOptions.newBuilder().withPath(remotePath).build())
        .start();
return new DropboxSearchResult(listing.getMatches());
{code}
The request does not set {{max_results}}, and {{SearchOptions}} of 
dropbox-core-sdk 7.1.1 uses 100 then (the constant in its no-argument 
constructor, read in the jar). {{SearchV2Result}} has {{getHasMore()}} and 
{{getCursor()}} for the next page ({{searchContinueV2(cursor)}}); the facade 
reads neither. So with the default options a search with more matches than one 
page returns the first page only: the body, and the {{FOUND_FILES}} header that 
is built from it, lack the rest. Nothing is logged and the exchange succeeds. 
The javadoc of the method says "@return a result object containing all the 
files found." (line 242), and the component has no option or header to ask for 
the next page, so a route cannot get the rest.

*{{get}} of a folder* is the lesser case ({{downloadFilesInFolder}}, lines 
307-318), used by the {{dropbox:get}} producer and consumer:
{code:java}
ListFolderResult folderResult = client.files().listFolder(path.equals("/") ? "" 
: path);
for (Metadata entry : folderResult.getEntries()) { ... download ... }
{code}
{{ListFolderResult}} has {{getHasMore()}} and {{getCursor()}} as well 
({{listFolderContinue(cursor)}}). The facade downloads the files of the first 
response only, although the javadoc says "Get the content of every file inside 
the remote path" (line 296). It needs a folder larger than one page of the 
listing; that size is decided by the service (the request sets no limit) and I 
did not measure it. {{get}} keeps the content of every file in memory, so it is 
not the operation for a very large folder anyway.

Not verified (nothing was run against Dropbox): the page size the service 
really uses for a search (100 is what the SDK asks for), whether the service 
limits the number of matches a search can return in total, and the page size of 
a folder listing.

h3. Reproduction

New {{DropboxAPIFacadePagingTest}} (3 tests): the Dropbox client is a Mockito 
mock that answers a search and a folder listing in three pages (page, 
{{has_more}}, cursor; the fake fails the test after 10 continue requests). On 
main 2 tests fail:
* {{searchReturnsTheMatchesOfEveryPage}}: {{the matches of the three pages, 
each once and in order ==> expected: <[/docs/report-1.txt, /docs/report-2.txt, 
/docs/report-3.txt, /docs/report-4.txt, /docs/report-5.txt]> but was: 
<[/docs/report-1.txt, /docs/report-2.txt]>}}.
* {{getDownloadsTheFilesOfEveryPageOfTheFolder}}: {{the files of the three 
pages, each once and in order ==> expected: <[/docs/a.txt, /docs/b.txt, 
/docs/c.txt, /docs/d.txt]> but was: <[/docs/a.txt, /docs/b.txt]>}}.
The third, {{resultThatFitsInOnePageNeedsNoFurtherRequest}}, passes on main as 
well; it is a guard that a result of one page sends no continue request (a 
folder listing returns a cursor with its last page too).

h3. Proposed fix

In {{DropboxAPIFacade}}: {{search}} calls {{searchContinueV2}} with the cursor 
of the response while {{getHasMore()}} is true (and a cursor is present) and 
returns the matches of all pages; {{downloadFilesInFolder}} calls 
{{listFolderContinue}} the same way and downloads the files of each page. The 
order is the order of the service. {{mockito-core}} is added to the module as a 
test dependency.

Behaviour change, with a 4.24 upgrade-guide entry: a search returns the matches 
of every page, and {{get}} of a large folder downloads all its files. 
Consequences that a user can notice:
* a search with many matches sends one request per page instead of one request; 
the search consumer does so at every poll;
* when a request for a later page fails, the operation fails and the matches 
that were already read are not returned. That is as before for a failed search, 
but there are now more requests that can fail;
* the number of pages is decided by the service ({{has_more}}); the facade has 
no limit of its own;
* {{get}} keeps the content of all files in the message body, as before, so a 
large folder needs more memory than when only the first page was read.
No new option. camel-dropbox unit tests with the fix: 4, 0 failures (the 
integration tests need a Dropbox account and were not run).

Found with a Lean 4 model of a token-paged service: the collection is a list, a 
response is a {{take}} / {{drop}} slice of it plus a token when items remain. 
Property: the result is the collection, every item once and in order. 
Counterexample for "first page only": with a page of two, {{[1, 2, 3]}} gives 
{{[1, 2]}} ({{cex_third_match_dropped}}); with a page of 100, 101 matches give 
100 ({{cex_101_matches}}); {{main_complete_iff}}: the first page is the whole 
result exactly when the result fits in one page. In the model, following the 
token returns the whole list ({{fix_meets_spec}}) and equals main's result when 
it fits in one page ({{fix_eq_main_one_page}}). The model says nothing about 
the real service. Then confirmed with the real class as described.

Affected: main (4.23.0-SNAPSHOT at c3829069cb72), and the release branches 
camel-4.18.x (c3fd5317a5a0), camel-4.22.x (2821f284056c) and camel-4.23.x 
(197e70656a41): {{DropboxAPIFacade}} has the same lines on all three (javadoc 
at 242 and 296, {{searchV2Builder}} at 252, {{listFolder}} at 309, no 
{{getHasMore}} / {{getCursor}} / continue call in the file). Read, not run.

Duplicate check (2026-10-12): JIRA text "dropbox search has_more" (2, 
unrelated: CAMEL-13025, CAMEL-12955), "dropbox search results truncated" (1, 
unrelated: CAMEL-12955), "dropbox listFolder" (none); GitHub pull requests 
"dropbox search": none about the result of a search; no open pull request 
touches camel-dropbox.

_Filed with Claude Code on behalf of allthingssecurity._




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to