@@ -36,57 +36,118 @@ public function enable(): void {
3636
3737 /**
3838 * This initializes the plugin.
39- *
40- * @param \Sabre\DAV\Server $server Sabre server
41- *
42- * @return void
43- * @throws MethodNotAllowed
39+ * It is ONLY initialized by the server on a file drop request.
4440 */
4541 public function initialize (\Sabre \DAV \Server $ server ): void {
4642 $ server ->on ('beforeMethod:* ' , [$ this , 'beforeMethod ' ], 999 );
43+ $ server ->on ('method:MKCOL ' , [$ this , 'onMkcol ' ]);
4744 $ this ->enabled = false ;
4845 }
4946
50- public function beforeMethod (RequestInterface $ request , ResponseInterface $ response ): void {
47+ public function onMkcol (RequestInterface $ request , ResponseInterface $ response ) {
48+ // If this is a folder creation request we need
49+ // to fake a success so we can pretend every
50+ // folder now exists.
51+ $ response ->setStatus (201 );
52+ return false ;
53+ }
54+
55+ public function beforeMethod (RequestInterface $ request , ResponseInterface $ response ) {
5156 if (!$ this ->enabled || $ this ->share === null || $ this ->view === null ) {
5257 return ;
5358 }
5459
55- // Only allow file drop
60+ // Retrieve the nickname from the request
61+ $ nickName = $ request ->hasHeader ('X-NC-Nickname ' )
62+ ? trim (urldecode ($ request ->getHeader ('X-NC-Nickname ' )))
63+ : null ;
64+
65+ //
5666 if ($ request ->getMethod () !== 'PUT ' ) {
57- throw new MethodNotAllowed ('Only PUT is allowed on files drop ' );
67+ // If uploading subfolders we need to ensure they get created
68+ // within the nickname folder
69+ if ($ request ->getMethod () === 'MKCOL ' ) {
70+ if (!$ nickName ) {
71+ throw new MethodNotAllowed ('A nickname header is required when uploading subfolders ' );
72+ }
73+ } else {
74+ throw new MethodNotAllowed ('Only PUT is allowed on files drop ' );
75+ }
76+ }
77+
78+ // If this is a folder creation request
79+ // let's stop there and let the onMkcol handle it
80+ if ($ request ->getMethod () === 'MKCOL ' ) {
81+ return ;
5882 }
5983
60- // Always upload at the root level
61- $ path = explode ('/ ' , $ request ->getPath ());
62- $ path = array_pop ($ path );
84+ // Now if we create a file, we need to create the
85+ // full path along the way. We'll only handle conflict
86+ // resolution on file conflicts, but not on folders.
87+
88+ // e.g files/dCP8yn3N86EK9sL/Folder/image.jpg
89+ $ path = $ request ->getPath ();
90+ $ token = $ this ->share ->getToken ();
91+
92+ // e.g files/dCP8yn3N86EK9sL
93+ $ rootPath = substr ($ path , 0 , strpos ($ path , $ token ) + strlen ($ token ));
94+ // e.g /Folder/image.jpg
95+ $ relativePath = substr ($ path , strlen ($ rootPath ));
6396
6497 // Extract the attributes for the file request
6598 $ isFileRequest = false ;
6699 $ attributes = $ this ->share ->getAttributes ();
67- $ nickName = $ request ->hasHeader ('X-NC-Nickname ' ) ? urldecode ($ request ->getHeader ('X-NC-Nickname ' )) : null ;
68100 if ($ attributes !== null ) {
69101 $ isFileRequest = $ attributes ->getAttribute ('fileRequest ' , 'enabled ' ) === true ;
70102 }
71103
72104 // We need a valid nickname for file requests
73- if ($ isFileRequest && ( $ nickName == null || trim ( $ nickName ) === '' ) ) {
74- throw new MethodNotAllowed ('Nickname is required for file requests ' );
105+ if ($ isFileRequest && ! $ nickName ) {
106+ throw new MethodNotAllowed ('A nickname header is required for file requests ' );
75107 }
76108
77- // If this is a file request we need to create a folder for the user
78- if ($ isFileRequest ) {
79- // Check if the folder already exists
80- if (!($ this ->view ->file_exists ($ nickName ) === true )) {
81- $ this ->view ->mkdir ($ nickName );
82- }
109+ // If we have a nickname, let's put everything inside
110+ if ($ nickName ) {
83111 // Put all files in the subfolder
84- $ path = $ nickName . '/ ' . $ path ;
112+ $ relativePath = '/ ' . $ nickName . '/ ' . $ relativePath ;
113+ $ relativePath = str_replace ('// ' , '/ ' , $ relativePath );
85114 }
86115
87- $ newName = \OC_Helper::buildNotExistingFileNameForView ('/ ' , $ path , $ this ->view );
88- $ url = $ request ->getBaseUrl () . '/files/ ' . $ this ->share ->getToken () . $ newName ;
116+ // Create the folders along the way
117+ $ folders = $ this ->getPathSegments (dirname ($ relativePath ));
118+ foreach ($ folders as $ folder ) {
119+ if ($ folder === '' ) continue ; // skip empty parts
120+ if (!$ this ->view ->file_exists ($ folder )) {
121+ $ this ->view ->mkdir ($ folder );
122+ }
123+ }
124+
125+ // Finally handle conflicts on the end files
126+ $ noConflictPath = \OC_Helper::buildNotExistingFileNameForView (dirname ($ relativePath ), basename ($ relativePath ), $ this ->view );
127+ $ path = '/files/ ' . $ token . '/ ' . $ noConflictPath ;
128+ $ url = $ request ->getBaseUrl () . str_replace ('// ' , '/ ' , $ path );
89129 $ request ->setUrl ($ url );
90130 }
91131
132+ private function getPathSegments (string $ path ): array {
133+ // Normalize slashes and remove trailing slash
134+ $ path = rtrim (str_replace ('\\' , '/ ' , $ path ), '/ ' );
135+
136+ // Handle absolute paths starting with /
137+ $ isAbsolute = str_starts_with ($ path , '/ ' );
138+
139+ $ segments = explode ('/ ' , $ path );
140+
141+ // Add back the leading slash for the first segment if needed
142+ $ result = [];
143+ $ current = $ isAbsolute ? '/ ' : '' ;
144+
145+ foreach ($ segments as $ segment ) {
146+ if ($ segment === '' ) continue ; // skip empty parts
147+ $ current = rtrim ($ current , '/ ' ) . '/ ' . $ segment ;
148+ $ result [] = $ current ;
149+ }
150+
151+ return $ result ;
152+ }
92153}
0 commit comments