wasDAVClient – Blame information for rev 5
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
5 | office | 1 | /////////////////////////////////////////////////////////////////////////// |
2 | // Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // |
||
3 | // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // |
||
4 | // rights of fair usage, the disclaimer and warranty conditions. // |
||
5 | /////////////////////////////////////////////////////////////////////////// |
||
6 | // Originally based on: WebDAV .NET client by Sergey Kazantsev |
||
7 | |||
8 | using System; |
||
1 | office | 9 | using System.Collections.Generic; |
10 | using System.IO; |
||
11 | using System.Linq; |
||
12 | using System.Net; |
||
13 | using System.Net.Http; |
||
14 | using System.Net.Http.Headers; |
||
15 | using System.Text; |
||
16 | using System.Threading.Tasks; |
||
17 | using wasDAVClient.Helpers; |
||
18 | using wasDAVClient.Model; |
||
19 | |||
20 | namespace wasDAVClient |
||
21 | { |
||
4 | office | 22 | public class Client : IClient, IDisposable |
1 | office | 23 | { |
24 | private const int HttpStatusCode_MultiStatus = 207; |
||
25 | |||
26 | // http://webdav.org/specs/rfc4918.html#METHOD_PROPFIND |
||
27 | private const string PropFindRequestContent = |
||
28 | "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + |
||
29 | "<propfind xmlns=\"DAV:\">" + |
||
30 | "<allprop/>" + |
||
31 | //" <propname/>" + |
||
32 | //" <prop>" + |
||
33 | //" <creationdate/>" + |
||
34 | //" <getlastmodified/>" + |
||
35 | //" <displayname/>" + |
||
36 | //" <getcontentlength/>" + |
||
37 | //" <getcontenttype/>" + |
||
38 | //" <getetag/>" + |
||
39 | //" <resourcetype/>" + |
||
40 | //" </prop> " + |
||
41 | "</propfind>"; |
||
42 | |||
3 | office | 43 | private static readonly HttpMethod PropFind = new HttpMethod("PROPFIND"); |
44 | private static readonly HttpMethod MoveMethod = new HttpMethod("MOVE"); |
||
45 | |||
46 | private static readonly HttpMethod MkCol = new HttpMethod(WebRequestMethods.Http.MkCol); |
||
47 | |||
1 | office | 48 | private static readonly string AssemblyVersion = typeof(IClient).Assembly.GetName().Version.ToString(); |
49 | |||
50 | private readonly HttpClient _client; |
||
51 | private readonly HttpClient _uploadClient; |
||
52 | private string _basePath = "/"; |
||
53 | |||
54 | private string _encodedBasePath; |
||
3 | office | 55 | private string _server; |
1 | office | 56 | |
5 | office | 57 | public Client(ICredentials credential = null) |
3 | office | 58 | { |
59 | var handler = new HttpClientHandler(); |
||
1 | office | 60 | |
3 | office | 61 | if (handler.SupportsProxy) |
62 | handler.Proxy = WebRequest.DefaultWebProxy; |
||
63 | |||
64 | if (handler.SupportsAutomaticDecompression) |
||
65 | handler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; |
||
66 | |||
67 | if (credential != null) |
||
68 | { |
||
69 | handler.Credentials = credential; |
||
70 | handler.PreAuthenticate = true; |
||
71 | } |
||
72 | |||
5 | office | 73 | _client = new HttpClient(handler) {Timeout = TimeSpan.FromSeconds(Timeout)}; |
3 | office | 74 | _client.DefaultRequestHeaders.ExpectContinue = false; |
75 | |||
5 | office | 76 | _uploadClient = new HttpClient(handler) {Timeout = TimeSpan.FromSeconds(Timeout)}; |
3 | office | 77 | _uploadClient.DefaultRequestHeaders.ExpectContinue = false; |
78 | } |
||
79 | |||
1 | office | 80 | #region WebDAV connection parameters |
81 | |||
82 | /// <summary> |
||
3 | office | 83 | /// Specify the WebDAV hostname (required). |
1 | office | 84 | /// </summary> |
85 | public string Server |
||
86 | { |
||
3 | office | 87 | get { return _server; } |
5 | office | 88 | set { _server = value.TrimEnd('/'); } |
1 | office | 89 | } |
90 | |||
91 | /// <summary> |
||
3 | office | 92 | /// Specify the path of a WebDAV directory to use as 'root' (default: /) |
1 | office | 93 | /// </summary> |
94 | public string BasePath |
||
95 | { |
||
3 | office | 96 | get { return _basePath; } |
1 | office | 97 | set |
98 | { |
||
99 | value = value.Trim('/'); |
||
100 | if (string.IsNullOrEmpty(value)) |
||
101 | _basePath = "/"; |
||
102 | else |
||
103 | _basePath = "/" + value + "/"; |
||
104 | } |
||
105 | } |
||
106 | |||
107 | /// <summary> |
||
3 | office | 108 | /// Specify an port (default: null = auto-detect) |
1 | office | 109 | /// </summary> |
3 | office | 110 | public int? Port { get; set; } |
1 | office | 111 | |
112 | /// <summary> |
||
3 | office | 113 | /// Specify the UserAgent (and UserAgent version) string to use in requests |
1 | office | 114 | /// </summary> |
3 | office | 115 | public string UserAgent { get; set; } |
1 | office | 116 | |
117 | /// <summary> |
||
3 | office | 118 | /// Specify the UserAgent (and UserAgent version) string to use in requests |
1 | office | 119 | /// </summary> |
3 | office | 120 | public string UserAgentVersion { get; set; } |
1 | office | 121 | |
3 | office | 122 | /// <summary> |
123 | /// The HTTP request timeout in seconds. |
||
124 | /// </summary> |
||
125 | public int Timeout { get; set; } = 60; |
||
126 | |||
1 | office | 127 | #endregion |
128 | |||
129 | #region WebDAV operations |
||
130 | |||
131 | /// <summary> |
||
3 | office | 132 | /// List all files present on the server. |
1 | office | 133 | /// </summary> |
134 | /// <param name="path">List only files in this path</param> |
||
135 | /// <param name="depth">Recursion depth</param> |
||
136 | /// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
||
137 | public async Task<IEnumerable<Item>> List(string path = "/", int? depth = 1) |
||
138 | { |
||
139 | var listUri = await GetServerUrl(path, true).ConfigureAwait(false); |
||
140 | |||
141 | // Depth header: http://webdav.org/specs/rfc4918.html#rfc.section.9.1.4 |
||
142 | IDictionary<string, string> headers = new Dictionary<string, string>(); |
||
143 | if (depth != null) |
||
144 | { |
||
145 | headers.Add("Depth", depth.ToString()); |
||
146 | } |
||
147 | |||
148 | HttpResponseMessage response = null; |
||
149 | |||
150 | try |
||
151 | { |
||
3 | office | 152 | response = |
153 | await |
||
154 | HttpRequest(listUri.Uri, PropFind, headers, Encoding.UTF8.GetBytes(PropFindRequestContent)) |
||
155 | .ConfigureAwait(false); |
||
1 | office | 156 | |
157 | if (response.StatusCode != HttpStatusCode.OK && |
||
3 | office | 158 | (int) response.StatusCode != HttpStatusCode_MultiStatus) |
1 | office | 159 | { |
3 | office | 160 | throw new wasDAVException((int) response.StatusCode, "Failed retrieving items in folder."); |
1 | office | 161 | } |
162 | |||
163 | using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) |
||
164 | { |
||
165 | var items = ResponseParser.ParseItems(stream); |
||
166 | |||
167 | if (items == null) |
||
168 | { |
||
169 | throw new wasDAVException("Failed deserializing data returned from server."); |
||
170 | } |
||
171 | |||
172 | var listUrl = listUri.ToString(); |
||
173 | |||
5 | office | 174 | return items.AsParallel().Select(async item => |
1 | office | 175 | { |
5 | office | 176 | switch (!item.IsCollection) |
1 | office | 177 | { |
5 | office | 178 | case true: |
179 | return item; |
||
180 | default: |
||
181 | // If it's not the requested parent folder, add it to the result |
||
182 | if (!string.Equals((await GetServerUrl(item.Href, true).ConfigureAwait(false)).ToString(), |
||
183 | listUrl, StringComparison.CurrentCultureIgnoreCase)) |
||
184 | { |
||
185 | return item; |
||
186 | } |
||
187 | break; |
||
1 | office | 188 | } |
5 | office | 189 | |
190 | return null; |
||
191 | }).Select(o => o.Result).OfType<Item>(); |
||
1 | office | 192 | } |
193 | } |
||
194 | finally |
||
195 | { |
||
5 | office | 196 | response?.Dispose(); |
1 | office | 197 | } |
198 | } |
||
199 | |||
200 | /// <summary> |
||
3 | office | 201 | /// List all files present on the server. |
1 | office | 202 | /// </summary> |
203 | /// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
||
204 | public async Task<Item> GetFolder(string path = "/") |
||
205 | { |
||
5 | office | 206 | return await Get((await GetServerUrl(path, true).ConfigureAwait(false)).Uri).ConfigureAwait(false); |
1 | office | 207 | } |
208 | |||
209 | /// <summary> |
||
3 | office | 210 | /// List all files present on the server. |
1 | office | 211 | /// </summary> |
212 | /// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
||
213 | public async Task<Item> GetFile(string path = "/") |
||
214 | { |
||
5 | office | 215 | return await Get((await GetServerUrl(path, false).ConfigureAwait(false)).Uri).ConfigureAwait(false); |
1 | office | 216 | } |
217 | |||
218 | |||
219 | /// <summary> |
||
3 | office | 220 | /// List all files present on the server. |
1 | office | 221 | /// </summary> |
222 | /// <returns>A list of files (entries without a trailing slash) and directories (entries with a trailing slash)</returns> |
||
5 | office | 223 | private async Task<Item> Get(Uri listUri) |
1 | office | 224 | { |
225 | // Depth header: http://webdav.org/specs/rfc4918.html#rfc.section.9.1.4 |
||
226 | IDictionary<string, string> headers = new Dictionary<string, string>(); |
||
227 | headers.Add("Depth", "0"); |
||
228 | |||
229 | HttpResponseMessage response = null; |
||
230 | |||
231 | try |
||
232 | { |
||
3 | office | 233 | response = |
234 | await |
||
235 | HttpRequest(listUri, PropFind, headers, Encoding.UTF8.GetBytes(PropFindRequestContent)) |
||
236 | .ConfigureAwait(false); |
||
1 | office | 237 | |
238 | if (response.StatusCode != HttpStatusCode.OK && |
||
3 | office | 239 | (int) response.StatusCode != HttpStatusCode_MultiStatus) |
1 | office | 240 | { |
3 | office | 241 | throw new wasDAVException((int) response.StatusCode, |
5 | office | 242 | $"Failed retrieving item/folder (Status Code: {response.StatusCode})"); |
1 | office | 243 | } |
244 | |||
245 | using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) |
||
246 | { |
||
247 | var result = ResponseParser.ParseItem(stream); |
||
248 | |||
249 | if (result == null) |
||
250 | { |
||
251 | throw new wasDAVException("Failed deserializing data returned from server."); |
||
252 | } |
||
253 | |||
254 | return result; |
||
255 | } |
||
256 | } |
||
257 | finally |
||
258 | { |
||
5 | office | 259 | response?.Dispose(); |
1 | office | 260 | } |
261 | } |
||
262 | |||
263 | /// <summary> |
||
3 | office | 264 | /// Download a file from the server |
1 | office | 265 | /// </summary> |
266 | /// <param name="remoteFilePath">Source path and filename of the file on the server</param> |
||
267 | public async Task<Stream> Download(string remoteFilePath) |
||
268 | { |
||
269 | // Should not have a trailing slash. |
||
270 | var downloadUri = await GetServerUrl(remoteFilePath, false).ConfigureAwait(false); |
||
271 | |||
3 | office | 272 | var dictionary = new Dictionary<string, string> {{"translate", "f"}}; |
1 | office | 273 | var response = await HttpRequest(downloadUri.Uri, HttpMethod.Get, dictionary).ConfigureAwait(false); |
274 | if (response.StatusCode != HttpStatusCode.OK) |
||
275 | { |
||
3 | office | 276 | throw new wasDAVException((int) response.StatusCode, "Failed retrieving file."); |
1 | office | 277 | } |
278 | return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); |
||
279 | } |
||
280 | |||
281 | /// <summary> |
||
3 | office | 282 | /// Download a file from the server |
1 | office | 283 | /// </summary> |
284 | /// <param name="remoteFilePath">Source path and filename of the file on the server</param> |
||
285 | /// <param name="content"></param> |
||
286 | /// <param name="name"></param> |
||
287 | public async Task<bool> Upload(string remoteFilePath, Stream content, string name) |
||
288 | { |
||
289 | // Should not have a trailing slash. |
||
3 | office | 290 | var uploadUri = |
291 | await GetServerUrl(remoteFilePath.TrimEnd('/') + "/" + name.TrimStart('/'), false).ConfigureAwait(false); |
||
1 | office | 292 | |
293 | HttpResponseMessage response = null; |
||
294 | |||
295 | try |
||
296 | { |
||
297 | response = await HttpUploadRequest(uploadUri.Uri, HttpMethod.Put, content).ConfigureAwait(false); |
||
298 | |||
299 | if (response.StatusCode != HttpStatusCode.OK && |
||
300 | response.StatusCode != HttpStatusCode.NoContent && |
||
301 | response.StatusCode != HttpStatusCode.Created) |
||
302 | { |
||
3 | office | 303 | throw new wasDAVException((int) response.StatusCode, "Failed uploading file."); |
1 | office | 304 | } |
305 | |||
306 | return response.IsSuccessStatusCode; |
||
307 | } |
||
308 | finally |
||
309 | { |
||
5 | office | 310 | response?.Dispose(); |
1 | office | 311 | } |
312 | } |
||
313 | |||
314 | |||
315 | /// <summary> |
||
3 | office | 316 | /// Create a directory on the server |
1 | office | 317 | /// </summary> |
5 | office | 318 | /// <param name="remotePath">Destination path of the directory on the server.</param> |
319 | /// <param name="name">The name of the folder to create.</param> |
||
1 | office | 320 | public async Task<bool> CreateDir(string remotePath, string name) |
321 | { |
||
322 | // Should not have a trailing slash. |
||
3 | office | 323 | var dirUri = |
324 | await GetServerUrl(remotePath.TrimEnd('/') + "/" + name.TrimStart('/'), false).ConfigureAwait(false); |
||
1 | office | 325 | |
326 | HttpResponseMessage response = null; |
||
327 | |||
328 | try |
||
329 | { |
||
330 | response = await HttpRequest(dirUri.Uri, MkCol).ConfigureAwait(false); |
||
331 | |||
332 | if (response.StatusCode == HttpStatusCode.Conflict) |
||
3 | office | 333 | throw new wasDAVConflictException((int) response.StatusCode, "Failed creating folder."); |
1 | office | 334 | |
335 | if (response.StatusCode != HttpStatusCode.OK && |
||
336 | response.StatusCode != HttpStatusCode.NoContent && |
||
337 | response.StatusCode != HttpStatusCode.Created) |
||
338 | { |
||
3 | office | 339 | throw new wasDAVException((int) response.StatusCode, "Failed creating folder."); |
1 | office | 340 | } |
341 | |||
342 | return response.IsSuccessStatusCode; |
||
343 | } |
||
344 | finally |
||
345 | { |
||
5 | office | 346 | response?.Dispose(); |
1 | office | 347 | } |
348 | } |
||
349 | |||
350 | public async Task DeleteFolder(string href) |
||
351 | { |
||
5 | office | 352 | await Delete((await GetServerUrl(href, true).ConfigureAwait(false)).Uri).ConfigureAwait(false); |
1 | office | 353 | } |
354 | |||
355 | public async Task DeleteFile(string href) |
||
356 | { |
||
5 | office | 357 | await Delete((await GetServerUrl(href, false).ConfigureAwait(false)).Uri).ConfigureAwait(false); |
1 | office | 358 | } |
359 | |||
360 | |||
361 | private async Task Delete(Uri listUri) |
||
362 | { |
||
363 | var response = await HttpRequest(listUri, HttpMethod.Delete).ConfigureAwait(false); |
||
364 | |||
365 | if (response.StatusCode != HttpStatusCode.OK && |
||
366 | response.StatusCode != HttpStatusCode.NoContent) |
||
367 | { |
||
3 | office | 368 | throw new wasDAVException((int) response.StatusCode, "Failed deleting item."); |
1 | office | 369 | } |
370 | } |
||
371 | |||
372 | public async Task<bool> MoveFolder(string srcFolderPath, string dstFolderPath) |
||
373 | { |
||
374 | // Should have a trailing slash. |
||
5 | office | 375 | return |
376 | await |
||
377 | Move((await GetServerUrl(srcFolderPath, true).ConfigureAwait(false)).Uri, |
||
378 | (await GetServerUrl(dstFolderPath, true).ConfigureAwait(false)).Uri).ConfigureAwait(false); |
||
1 | office | 379 | } |
380 | |||
381 | public async Task<bool> MoveFile(string srcFilePath, string dstFilePath) |
||
382 | { |
||
383 | // Should not have a trailing slash. |
||
5 | office | 384 | return |
385 | await |
||
386 | Move((await GetServerUrl(srcFilePath, false).ConfigureAwait(false)).Uri, |
||
387 | (await GetServerUrl(dstFilePath, false).ConfigureAwait(false)).Uri).ConfigureAwait(false); |
||
1 | office | 388 | } |
389 | |||
390 | |||
391 | private async Task<bool> Move(Uri srcUri, Uri dstUri) |
||
392 | { |
||
393 | const string requestContent = "MOVE"; |
||
394 | |||
5 | office | 395 | IDictionary<string, string> headers = new Dictionary<string, string> |
396 | { |
||
397 | {"Destination", dstUri.ToString()} |
||
398 | }; |
||
1 | office | 399 | |
3 | office | 400 | var response = |
401 | await |
||
402 | HttpRequest(srcUri, MoveMethod, headers, Encoding.UTF8.GetBytes(requestContent)) |
||
403 | .ConfigureAwait(false); |
||
1 | office | 404 | |
405 | if (response.StatusCode != HttpStatusCode.OK && |
||
406 | response.StatusCode != HttpStatusCode.Created) |
||
407 | { |
||
3 | office | 408 | throw new wasDAVException((int) response.StatusCode, "Failed moving file."); |
1 | office | 409 | } |
410 | |||
411 | return response.IsSuccessStatusCode; |
||
412 | } |
||
413 | |||
414 | #endregion |
||
415 | |||
416 | #region Server communication |
||
417 | |||
418 | /// <summary> |
||
3 | office | 419 | /// Perform the WebDAV call and fire the callback when finished. |
1 | office | 420 | /// </summary> |
421 | /// <param name="uri"></param> |
||
422 | /// <param name="method"></param> |
||
423 | /// <param name="headers"></param> |
||
424 | /// <param name="content"></param> |
||
3 | office | 425 | private async Task<HttpResponseMessage> HttpRequest(Uri uri, HttpMethod method, |
426 | IDictionary<string, string> headers = null, byte[] content = null) |
||
1 | office | 427 | { |
428 | using (var request = new HttpRequestMessage(method, uri)) |
||
429 | { |
||
430 | request.Headers.Connection.Add("Keep-Alive"); |
||
5 | office | 431 | request.Headers.UserAgent.Add(!string.IsNullOrWhiteSpace(UserAgent) |
432 | ? new ProductInfoHeaderValue(UserAgent, UserAgentVersion) |
||
433 | : new ProductInfoHeaderValue("WebDAVClient", AssemblyVersion)); |
||
1 | office | 434 | |
435 | if (headers != null) |
||
436 | { |
||
3 | office | 437 | foreach (var key in headers.Keys) |
1 | office | 438 | { |
439 | request.Headers.Add(key, headers[key]); |
||
440 | } |
||
441 | } |
||
442 | |||
443 | // Need to send along content? |
||
444 | if (content != null) |
||
445 | { |
||
446 | request.Content = new ByteArrayContent(content); |
||
447 | request.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml"); |
||
448 | } |
||
449 | |||
450 | return await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); |
||
451 | } |
||
452 | } |
||
453 | |||
454 | /// <summary> |
||
3 | office | 455 | /// Perform the WebDAV call and fire the callback when finished. |
1 | office | 456 | /// </summary> |
457 | /// <param name="uri"></param> |
||
458 | /// <param name="headers"></param> |
||
459 | /// <param name="method"></param> |
||
460 | /// <param name="content"></param> |
||
3 | office | 461 | private async Task<HttpResponseMessage> HttpUploadRequest(Uri uri, HttpMethod method, Stream content, |
462 | IDictionary<string, string> headers = null) |
||
1 | office | 463 | { |
464 | using (var request = new HttpRequestMessage(method, uri)) |
||
465 | { |
||
466 | request.Headers.Connection.Add("Keep-Alive"); |
||
5 | office | 467 | request.Headers.UserAgent.Add(!string.IsNullOrWhiteSpace(UserAgent) |
468 | ? new ProductInfoHeaderValue(UserAgent, UserAgentVersion) |
||
469 | : new ProductInfoHeaderValue("WebDAVClient", AssemblyVersion)); |
||
1 | office | 470 | |
471 | if (headers != null) |
||
472 | { |
||
3 | office | 473 | foreach (var key in headers.Keys) |
1 | office | 474 | { |
475 | request.Headers.Add(key, headers[key]); |
||
476 | } |
||
477 | } |
||
478 | |||
479 | // Need to send along content? |
||
480 | if (content != null) |
||
481 | { |
||
482 | request.Content = new StreamContent(content); |
||
483 | } |
||
484 | |||
5 | office | 485 | return await (_uploadClient ?? _client).SendAsync(request).ConfigureAwait(false); |
1 | office | 486 | } |
487 | } |
||
488 | |||
489 | /// <summary> |
||
3 | office | 490 | /// Try to create an Uri with kind UriKind.Absolute |
491 | /// This particular implementation also works on Mono/Linux |
||
492 | /// It seems that on Mono it is expected behaviour that uris |
||
493 | /// of kind /a/b are indeed absolute uris since it referes to a file in /a/b. |
||
494 | /// https://bugzilla.xamarin.com/show_bug.cgi?id=30854 |
||
1 | office | 495 | /// </summary> |
496 | /// <param name="uriString"></param> |
||
497 | /// <param name="uriResult"></param> |
||
498 | /// <returns></returns> |
||
499 | private static bool TryCreateAbsolute(string uriString, out Uri uriResult) |
||
500 | { |
||
501 | return Uri.TryCreate(uriString, UriKind.Absolute, out uriResult) && uriResult.Scheme != Uri.UriSchemeFile; |
||
502 | } |
||
503 | |||
504 | private async Task<UriBuilder> GetServerUrl(string path, bool appendTrailingSlash) |
||
505 | { |
||
506 | // Resolve the base path on the server |
||
507 | if (_encodedBasePath == null) |
||
508 | { |
||
3 | office | 509 | var baseUri = new UriBuilder(_server) {Path = _basePath}; |
5 | office | 510 | var root = await Get(baseUri.Uri).ConfigureAwait(false); |
1 | office | 511 | |
512 | _encodedBasePath = root.Href; |
||
513 | } |
||
514 | |||
515 | |||
516 | // If we've been asked for the "root" folder |
||
517 | if (string.IsNullOrEmpty(path)) |
||
518 | { |
||
519 | // If the resolved base path is an absolute URI, use it |
||
520 | Uri absoluteBaseUri; |
||
521 | if (TryCreateAbsolute(_encodedBasePath, out absoluteBaseUri)) |
||
522 | { |
||
523 | return new UriBuilder(absoluteBaseUri); |
||
524 | } |
||
525 | |||
526 | // Otherwise, use the resolved base path relatively to the server |
||
3 | office | 527 | var baseUri = new UriBuilder(_server) {Path = _encodedBasePath}; |
1 | office | 528 | return baseUri; |
529 | } |
||
530 | |||
531 | // If the requested path is absolute, use it |
||
532 | Uri absoluteUri; |
||
533 | if (TryCreateAbsolute(path, out absoluteUri)) |
||
534 | { |
||
535 | var baseUri = new UriBuilder(absoluteUri); |
||
536 | return baseUri; |
||
537 | } |
||
538 | else |
||
539 | { |
||
540 | // Otherwise, create a URI relative to the server |
||
541 | UriBuilder baseUri; |
||
542 | if (TryCreateAbsolute(_encodedBasePath, out absoluteUri)) |
||
543 | { |
||
544 | baseUri = new UriBuilder(absoluteUri); |
||
545 | |||
546 | baseUri.Path = baseUri.Path.TrimEnd('/') + "/" + path.TrimStart('/'); |
||
547 | |||
548 | if (appendTrailingSlash && !baseUri.Path.EndsWith("/")) |
||
549 | baseUri.Path += "/"; |
||
550 | } |
||
551 | else |
||
552 | { |
||
553 | baseUri = new UriBuilder(_server); |
||
554 | |||
555 | // Ensure we don't add the base path twice |
||
556 | var finalPath = path; |
||
557 | if (!finalPath.StartsWith(_encodedBasePath, StringComparison.InvariantCultureIgnoreCase)) |
||
558 | { |
||
559 | finalPath = _encodedBasePath.TrimEnd('/') + "/" + path; |
||
560 | } |
||
561 | if (appendTrailingSlash) |
||
562 | finalPath = finalPath.TrimEnd('/') + "/"; |
||
563 | |||
564 | baseUri.Path = finalPath; |
||
565 | } |
||
566 | |||
567 | |||
568 | return baseUri; |
||
569 | } |
||
570 | } |
||
571 | |||
4 | office | 572 | public void Dispose() |
573 | { |
||
5 | office | 574 | _client?.Dispose(); |
575 | _uploadClient?.Dispose(); |
||
4 | office | 576 | } |
577 | |||
1 | office | 578 | #endregion |
579 | } |
||
3 | office | 580 | } |