@@ -36,57 +36,136 @@ 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 ) {
5148 if (!$ this ->enabled || $ this ->share === null || $ this ->view === null ) {
5249 return ;
5350 }
5451
55- // Only allow file drop
52+ // If this is a folder creation request we need
53+ // to fake a success so we can pretend every
54+ // folder now exists.
55+ $ response ->setStatus (201 );
56+ return false ;
57+ }
58+
59+ public function beforeMethod (RequestInterface $ request , ResponseInterface $ response ) {
60+ if (!$ this ->enabled || $ this ->share === null || $ this ->view === null ) {
61+ return ;
62+ }
63+
64+ // Retrieve the nickname from the request
65+ $ nickname = $ request ->hasHeader ('X-NC-Nickname ' )
66+ ? trim (urldecode ($ request ->getHeader ('X-NC-Nickname ' )))
67+ : null ;
68+
69+ //
5670 if ($ request ->getMethod () !== 'PUT ' ) {
57- throw new MethodNotAllowed ('Only PUT is allowed on files drop ' );
71+ // If uploading subfolders we need to ensure they get created
72+ // within the nickname folder
73+ if ($ request ->getMethod () === 'MKCOL ' ) {
74+ if (!$ nickname ) {
75+ throw new MethodNotAllowed ('A nickname header is required when uploading subfolders ' );
76+ }
77+ } else {
78+ throw new MethodNotAllowed ('Only PUT is allowed on files drop ' );
79+ }
80+ }
81+
82+ // If this is a folder creation request
83+ // let's stop there and let the onMkcol handle it
84+ if ($ request ->getMethod () === 'MKCOL ' ) {
85+ return ;
5886 }
5987
60- // Always upload at the root level
61- $ path = explode ('/ ' , $ request ->getPath ());
62- $ path = array_pop ($ path );
88+ // Now if we create a file, we need to create the
89+ // full path along the way. We'll only handle conflict
90+ // resolution on file conflicts, but not on folders.
91+
92+ // e.g files/dCP8yn3N86EK9sL/Folder/image.jpg
93+ $ path = $ request ->getPath ();
94+ $ token = $ this ->share ->getToken ();
95+
96+ // e.g files/dCP8yn3N86EK9sL
97+ $ rootPath = substr ($ path , 0 , strpos ($ path , $ token ) + strlen ($ token ));
98+ // e.g /Folder/image.jpg
99+ $ relativePath = substr ($ path , strlen ($ rootPath ));
100+ $ isRootUpload = substr_count ($ relativePath , '/ ' ) === 1 ;
63101
64102 // Extract the attributes for the file request
65103 $ isFileRequest = false ;
66104 $ attributes = $ this ->share ->getAttributes ();
67- $ nickName = $ request ->hasHeader ('X-NC-Nickname ' ) ? urldecode ($ request ->getHeader ('X-NC-Nickname ' )) : null ;
68105 if ($ attributes !== null ) {
69106 $ isFileRequest = $ attributes ->getAttribute ('fileRequest ' , 'enabled ' ) === true ;
70107 }
71108
72109 // 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 ' );
110+ if ($ isFileRequest && ! $ nickname ) {
111+ throw new MethodNotAllowed ('A nickname header is required for file requests ' );
75112 }
76113
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- }
114+ // We're only allowing the upload of
115+ // long path with subfolders if a nickname is set.
116+ // This prevents confusion when uploading files and help
117+ // classify them by uploaders.
118+ if (!$ nickname && !$ isRootUpload ) {
119+ throw new MethodNotAllowed ('A nickname header is required when uploading subfolders ' );
120+ }
121+
122+ // If we have a nickname, let's put everything inside
123+ if ($ nickname ) {
83124 // Put all files in the subfolder
84- $ path = $ nickName . '/ ' . $ path ;
125+ $ relativePath = '/ ' . $ nickname . '/ ' . $ relativePath ;
126+ $ relativePath = str_replace ('// ' , '/ ' , $ relativePath );
85127 }
86128
87- $ newName = \OC_Helper::buildNotExistingFileNameForView ('/ ' , $ path , $ this ->view );
88- $ url = $ request ->getBaseUrl () . '/files/ ' . $ this ->share ->getToken () . $ newName ;
129+ // Create the folders along the way
130+ $ folders = $ this ->getPathSegments (dirname ($ relativePath ));
131+ foreach ($ folders as $ folder ) {
132+ if ($ folder === '' ) {
133+ continue ;
134+ } // skip empty parts
135+ if (!$ this ->view ->file_exists ($ folder )) {
136+ $ this ->view ->mkdir ($ folder );
137+ }
138+ }
139+
140+ // Finally handle conflicts on the end files
141+ $ noConflictPath = \OC_Helper::buildNotExistingFileNameForView (dirname ($ relativePath ), basename ($ relativePath ), $ this ->view );
142+ $ path = '/files/ ' . $ token . '/ ' . $ noConflictPath ;
143+ $ url = $ request ->getBaseUrl () . str_replace ('// ' , '/ ' , $ path );
89144 $ request ->setUrl ($ url );
90145 }
91146
147+ private function getPathSegments (string $ path ): array {
148+ // Normalize slashes and remove trailing slash
149+ $ path = rtrim (str_replace ('\\' , '/ ' , $ path ), '/ ' );
150+
151+ // Handle absolute paths starting with /
152+ $ isAbsolute = str_starts_with ($ path , '/ ' );
153+
154+ $ segments = explode ('/ ' , $ path );
155+
156+ // Add back the leading slash for the first segment if needed
157+ $ result = [];
158+ $ current = $ isAbsolute ? '/ ' : '' ;
159+
160+ foreach ($ segments as $ segment ) {
161+ if ($ segment === '' ) {
162+ // skip empty parts
163+ continue ;
164+ }
165+ $ current = rtrim ($ current , '/ ' ) . '/ ' . $ segment ;
166+ $ result [] = $ current ;
167+ }
168+
169+ return $ result ;
170+ }
92171}
0 commit comments