Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@
"DescriptionWebdavurl": {
"message": "e.g. with nextcloud: https://example.com/remote.php/webdav/"
},
"LabelCustomHeaders": {
"message": "Custom Headers"
},
"DescriptionCustomHeaders": {
"message": "Optional: Add custom headers in key:value format, one per line. These will be added to all WebDAV requests."
},
"LabelNextcloudurl": {
"message": "Nextcloud URL"
},
Expand Down
6 changes: 6 additions & 0 deletions _locales/zh_CN/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@
},
"DescriptionWebdavurl": {
"message": "例如使用 NextCloud:https://your-domain.com/remote.php/webdav/"
},
"LabelCustomHeaders": {
"message": "自定义header"
},
"DescriptionCustomHeaders": {
"message": "可选:添加自定义 header 键值对,类似key1:value1, 每行一个。这些 header 将被添加到所有 WebDAV 请求中。"
},
"LabelNextcloudurl": {
"message": "Nextcloud URL"
Expand Down
23 changes: 16 additions & 7 deletions src/lib/adapters/WebDav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default class WebDavAdapter extends CachingAdapter {
allowRedirects: false,
passphrase: '',
allowNetwork: false,
customHeaders: {},
}
}

Expand Down Expand Up @@ -166,7 +167,8 @@ export default class WebDavAdapter extends CachingAdapter {
method: 'DELETE',
credentials: 'omit',
headers: {
Authorization: 'Basic ' + authString
Authorization: 'Basic ' + authString,
...(this.server.customHeaders || {}),
},
signal: this.abortSignal,
...(!this.server.allowRedirects && {redirect: 'manual'}),
Expand All @@ -176,7 +178,8 @@ export default class WebDavAdapter extends CachingAdapter {
url: fullUrl,
method: 'DELETE',
headers: {
Authorization: 'Basic ' + authString
Authorization: 'Basic ' + authString,
...(this.server.customHeaders || {}),
},
webFetchExtra: {
credentials: 'omit',
Expand Down Expand Up @@ -397,7 +400,8 @@ export default class WebDavAdapter extends CachingAdapter {
method: 'PUT',
headers: {
'Content-Type': content_type,
Authorization: 'Basic ' + authString
Authorization: 'Basic ' + authString,
...(this.server.customHeaders || {})
},
credentials: 'omit',
signal: this.abortSignal,
Expand Down Expand Up @@ -432,7 +436,8 @@ export default class WebDavAdapter extends CachingAdapter {
method: 'PUT',
headers: {
'Content-Type': content_type,
Authorization: 'Basic ' + authString
Authorization: 'Basic ' + authString,
...(this.server.customHeaders || {})
},
data
})
Expand Down Expand Up @@ -476,6 +481,7 @@ export default class WebDavAdapter extends CachingAdapter {
headers: {
Authorization: 'Basic ' + authString,
Depth: '0',
...(this.server.customHeaders || {}),
},
cache: 'no-store',
credentials: 'omit',
Expand Down Expand Up @@ -517,7 +523,8 @@ export default class WebDavAdapter extends CachingAdapter {
Authorization: 'Basic ' + authString,
Depth: '0',
Pragma: 'no-cache',
'Cache-Control': 'no-cache'
'Cache-Control': 'no-cache',
...(this.server.customHeaders || {}),
},
responseType: 'text'
})
Expand Down Expand Up @@ -548,7 +555,8 @@ export default class WebDavAdapter extends CachingAdapter {
res = await fetch(url,{
method: 'GET',
headers: {
Authorization: 'Basic ' + authString
Authorization: 'Basic ' + authString,
...(this.server.customHeaders || {})
},
cache: 'no-store',
credentials: 'omit',
Expand Down Expand Up @@ -587,7 +595,8 @@ export default class WebDavAdapter extends CachingAdapter {
headers: {
Authorization: 'Basic ' + authString,
Pragma: 'no-cache',
'Cache-Control': 'no-cache'
'Cache-Control': 'no-cache',
...(this.server.customHeaders || {})
},
responseType: 'text'
})
Expand Down
32 changes: 31 additions & 1 deletion src/ui/components/OptionsWebdav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@
:rules="[validateUrl]"
:label="t('LabelWebdavurl')"
@input="$emit('update:url', $event)" />
<v-textarea
:value="customHeadersText"
class="mt-2"
:label="t('LabelCustomHeaders')"
:hint="t('DescriptionCustomHeaders')"
:persistent-hint="true"
rows="3"
auto-grow
placeholder="Authorization=Bearer token&#10;X-Custom-Header=value"
@input="onCustomHeadersInput" />
<v-text-field
:value="username"
:label="t('LabelUsername')"
Expand Down Expand Up @@ -159,14 +169,31 @@ import OptionSyncIntervalEnabled from './OptionSyncIntervalEnabled.vue'
export default {
name: 'OptionsWebdav',
components: { OptionSyncIntervalEnabled, OptionAutoSync, OptionExportBookmarks, OptionAllowNetwork, OptionDownloadLogs, OptionAllowRedirects, OptionClientCert, OptionFailsafe, OptionSyncFolder, OptionDeleteAccount, OptionSyncStrategy, OptionResetCache, OptionSyncInterval, OptionNestedSync, OptionFileType, OptionPassphrase },
props: ['url', 'username', 'password','passphrase', 'includeCredentials', 'serverRoot', 'localRoot', 'allowNetwork', 'syncInterval', 'strategy', 'bookmark_file', 'nestedSync', 'failsafe', 'allowRedirects', 'bookmark_file_type', 'enabled', 'label', 'syncIntervalEnabled'],
props: ['url', 'username', 'password','passphrase', 'includeCredentials', 'serverRoot', 'localRoot', 'allowNetwork', 'syncInterval', 'strategy', 'bookmark_file', 'nestedSync', 'failsafe', 'allowRedirects', 'bookmark_file_type', 'enabled', 'label', 'syncIntervalEnabled', 'customHeaders'],
data() {
return {
panels: [0, 1],
showPassword: false,
showPassphrase: false,
}
},
computed: {
customHeadersText: {
get() {
if (!this.customHeaders) return ''
if (typeof this.customHeaders === 'string') return this.customHeaders
if (typeof this.customHeaders === 'object') {
return Object.entries(this.customHeaders)
.map(([key, value]) => `${key}=${value}`)
.join('\n')
}
return ''
},
set(value) {
this.$emit('update:customHeaders', value)
}
}
},
methods: {
validateUrl(str) {
try {
Expand All @@ -179,6 +206,9 @@ export default {
validateBookmarksFile(path) {
return path[0] !== '/' && path[path.length - 1] !== '/'
},
onCustomHeadersInput(value) {
this.customHeadersText = value
}
}
}
</script>
Expand Down
18 changes: 10 additions & 8 deletions src/ui/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,18 +112,20 @@ export const actionsDefinition = {
async [actions.DOWNLOAD_LOGS]({ commit, dispatch, state }, anonymous) {
await Logger.downloadLogs(anonymous)
},
async [actions.TEST_WEBDAV_SERVER]({commit, dispatch, state}, {rootUrl, username, password}) {
async [actions.TEST_WEBDAV_SERVER]({commit, dispatch, state}, {rootUrl, username, password, customHeaders = {}}) {
await dispatch(actions.REQUEST_NETWORK_PERMISSIONS)
const headers = {
'User-Agent': 'Floccus bookmarks sync',
Depth: '0',
Authorization: 'Basic ' + Base64.encode(
username + ':' + password
),
...customHeaders
}
let res = await fetch(`${rootUrl}`, {
method: 'PROPFIND',
credentials: 'omit',
headers: {
'User-Agent': 'Floccus bookmarks sync',
Depth: '0',
Authorization: 'Basic ' + Base64.encode(
username + ':' + password
)
}
headers: headers
})
if (res.status < 200 || res.status > 299) {
throw new Error('Could not connect to your webdav server at the specified URL. The server responded with HTTP status ' + res.status + ' to a PROPFIND request with Basic Auth on the URL you entered.')
Expand Down
37 changes: 36 additions & 1 deletion src/ui/views/NewAccount.vue
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,15 @@
:append-icon="showPassphrase ? 'mdi-eye' : 'mdi-eye-off'"
:type="showPassphrase ? 'text' : 'password'"
@click:append="showPassphrase = !showPassphrase" />
<v-textarea
v-model="customHeaders"
class="mt-2"
:label="t('LabelCustomHeaders')"
:hint="t('DescriptionCustomHeaders')"
:persistent-hint="true"
rows="3"
auto-grow
placeholder="Authorization=Bearer token&#10;X-Custom-Header=value" />
</v-form>
<div class="form-buttons">
<v-btn @click="currentStep--">
Expand Down Expand Up @@ -513,6 +522,7 @@ export default {
showPassphrase: false,
clickCountEnabled: false,
label: '',
customHeaders: '',
adapter: 'nextcloud-bookmarks',
adapters: [
{
Expand Down Expand Up @@ -585,6 +595,7 @@ export default {
...(this.adapter === 'google-drive' && {refreshToken: this.refreshToken}),
...(this.passphrase && {passphrase: this.passphrase}),
...(this.adapter === 'google-drive' && this.passphrase && {password: this.passphrase}),
...(this.adapter === 'webdav' && this.customHeaders && {customHeaders: this.parseCustomHeaders()}),
...(this.isBrowser && {localRoot: this.localRoot}),
syncInterval: this.syncInterval,
strategy: this.strategy,
Expand Down Expand Up @@ -637,7 +648,14 @@ export default {
this.isServerTestRunning = true
this.serverTestError = ''
try {
await this.$store.dispatch(actions.TEST_WEBDAV_SERVER, {rootUrl: this.server, username: this.username, password: this.password})
const customHeaders = this.parseCustomHeaders()
console.log('debug customerHeader:', customHeaders)
await this.$store.dispatch(actions.TEST_WEBDAV_SERVER, {
rootUrl: this.server,
username: this.username,
password: this.password,
customHeaders
})
this.serverTestSuccessful = true
this.currentStep++
} catch (e) {
Expand Down Expand Up @@ -689,6 +707,23 @@ export default {
validateBookmarksFileGoogle(path) {
return !path.includes('/')
},
parseCustomHeaders() {
const headers = {}
if (!this.customHeaders.trim()) return headers

const lines = this.customHeaders.split('\n')
lines.forEach(line => {
const trimmedLine = line.trim()
if (trimmedLine && trimmedLine.includes(':')) {
const [key, ...valueParts] = trimmedLine.split(':')
const value = valueParts.join(':').trim()
if (key.trim() && value) {
headers[key.trim()] = value
}
}
})
return headers
},
requestHistoryPermissions() {
this.$store.dispatch(actions.REQUEST_HISTORY_PERMISSIONS)
}
Expand Down