Compare commits

..

3 Commits

Author SHA1 Message Date
Lennart
eae8e7d768 version 0.4.12 2025-07-07 21:18:46 +02:00
Lennart
105718a4ca frontend: Add xml escaping to collection creation forms 2025-07-07 21:18:16 +02:00
Lennart
0e68f1bdce frontend: refactor collection list to allow for dialogs 2025-07-07 11:22:20 +02:00
11 changed files with 90 additions and 58 deletions

22
Cargo.lock generated
View File

@@ -2999,7 +2999,7 @@ dependencies = [
[[package]]
name = "rustical"
version = "0.4.11"
version = "0.4.12"
dependencies = [
"anyhow",
"argon2",
@@ -3042,7 +3042,7 @@ dependencies = [
[[package]]
name = "rustical_caldav"
version = "0.4.11"
version = "0.4.12"
dependencies = [
"async-std",
"async-trait",
@@ -3080,7 +3080,7 @@ dependencies = [
[[package]]
name = "rustical_carddav"
version = "0.4.11"
version = "0.4.12"
dependencies = [
"async-trait",
"axum",
@@ -3112,7 +3112,7 @@ dependencies = [
[[package]]
name = "rustical_dav"
version = "0.4.11"
version = "0.4.12"
dependencies = [
"async-trait",
"axum",
@@ -3137,7 +3137,7 @@ dependencies = [
[[package]]
name = "rustical_dav_push"
version = "0.4.11"
version = "0.4.12"
dependencies = [
"async-trait",
"axum",
@@ -3163,7 +3163,7 @@ dependencies = [
[[package]]
name = "rustical_frontend"
version = "0.4.11"
version = "0.4.12"
dependencies = [
"askama",
"askama_web",
@@ -3196,7 +3196,7 @@ dependencies = [
[[package]]
name = "rustical_ical"
version = "0.4.11"
version = "0.4.12"
dependencies = [
"axum",
"chrono",
@@ -3214,7 +3214,7 @@ dependencies = [
[[package]]
name = "rustical_oidc"
version = "0.4.11"
version = "0.4.12"
dependencies = [
"async-trait",
"axum",
@@ -3229,7 +3229,7 @@ dependencies = [
[[package]]
name = "rustical_store"
version = "0.4.11"
version = "0.4.12"
dependencies = [
"anyhow",
"async-trait",
@@ -3263,7 +3263,7 @@ dependencies = [
[[package]]
name = "rustical_store_sqlite"
version = "0.4.11"
version = "0.4.12"
dependencies = [
"async-trait",
"chrono",
@@ -3284,7 +3284,7 @@ dependencies = [
[[package]]
name = "rustical_xml"
version = "0.4.11"
version = "0.4.12"
dependencies = [
"quick-xml",
"thiserror 2.0.12",

View File

@@ -2,7 +2,7 @@
members = ["crates/*"]
[workspace.package]
version = "0.4.11"
version = "0.4.12"
edition = "2024"
description = "A CalDAV server"
repository = "https://github.com/lennart-k/rustical"

View File

@@ -2,6 +2,7 @@ import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Ref, createRef, ref } from 'lit/directives/ref.js';
import { createClient } from "webdav";
import { escapeXml } from ".";
@customElement("create-addressbook-form")
export class CreateAddressbookForm extends LitElement {
@@ -17,15 +18,15 @@ export class CreateAddressbookForm extends LitElement {
client = createClient("/carddav")
@property()
user: String = ''
user: string = ''
@property()
principal: String = ''
principal: string = ''
@property()
addr_id: String = ''
addr_id: string = ''
@property()
displayname: String = ''
displayname: string = ''
@property()
description: String = ''
description: string = ''
dialog: Ref<HTMLDialogElement> = createRef()
form: Ref<HTMLFormElement> = createRef()
@@ -85,8 +86,8 @@ export class CreateAddressbookForm extends LitElement {
<mkcol xmlns="DAV:" xmlns:CARD="urn:ietf:params:xml:ns:carddav">
<set>
<prop>
<displayname>${this.displayname}</displayname>
${this.description ? `<CARD:addressbook-description>${this.description}</CARD:addressbook-description>` : ''}
<displayname>${escapeXml(this.displayname)}</displayname>
${this.description ? `<CARD:addressbook-description>${escapeXml(this.description)}</CARD:addressbook-description>` : ''}
</prop>
</set>
</mkcol>

View File

@@ -2,6 +2,7 @@ import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Ref, createRef, ref } from 'lit/directives/ref.js';
import { createClient } from "webdav";
import { escapeXml } from ".";
@customElement("create-calendar-form")
export class CreateCalendarForm extends LitElement {
@@ -16,21 +17,21 @@ export class CreateCalendarForm extends LitElement {
client = createClient("/caldav")
@property()
user: String = ''
user: string = ''
@property()
principal: String = ''
principal: string = ''
@property()
cal_id: String = ''
cal_id: string = ''
@property()
displayname: String = ''
displayname: string = ''
@property()
description: String = ''
description: string = ''
@property()
color: String = ''
color: string = ''
@property()
isSubscription: boolean = false
@property()
subscriptionUrl: String = ''
subscriptionUrl: string = ''
@property()
components: Set<"VEVENT" | "VTODO" | "VJOURNAL"> = new Set()
@@ -123,12 +124,12 @@ export class CreateCalendarForm extends LitElement {
<mkcol xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/" xmlns:ICAL="http://apple.com/ns/ical/">
<set>
<prop>
<displayname>${this.displayname}</displayname>
${this.description ? `<CAL:calendar-description>${this.description}</CAL:calendar-description>` : ''}
${this.color ? `<ICAL:calendar-color>${this.color}</ICAL:calendar-color>` : ''}
${(this.isSubscription && this.subscriptionUrl) ? `<CS:source><href>${this.subscriptionUrl}</href></CS:source>` : ''}
<displayname>${escapeXml(this.displayname)}</displayname>
${this.description ? `<CAL:calendar-description>${escapeXml(this.description)}</CAL:calendar-description>` : ''}
${this.color ? `<ICAL:calendar-color>${escapeXml(this.color)}</ICAL:calendar-color>` : ''}
${(this.isSubscription && this.subscriptionUrl) ? `<CS:source><href>${escapeXml(this.subscriptionUrl)}</href></CS:source>` : ''}
<CAL:supported-calendar-component-set>
${Array.from(this.components.keys()).map(comp => `<CAL:comp name="${comp}" />`).join('\n')}
${Array.from(this.components.keys()).map(comp => `<CAL:comp name="${escapeXml(comp)}" />`).join('\n')}
</CAL:supported-calendar-component-set>
</prop>
</set>

View File

@@ -0,0 +1,7 @@
export function escapeXml(unsafe: string): string {
return unsafe.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
}

View File

@@ -1,6 +1,6 @@
import { i, x } from "./lit-z6_uA4GX.mjs";
import { n as n$1, t } from "./property-D0NJdseG.mjs";
import { e, n } from "./ref-CPp9J0V5.mjs";
import { e, n, a as escapeXml } from "./index-b86iLJlP.mjs";
import { a as an } from "./webdav-D0R7xCzX.mjs";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -84,8 +84,8 @@ let CreateAddressbookForm = class extends i {
<mkcol xmlns="DAV:" xmlns:CARD="urn:ietf:params:xml:ns:carddav">
<set>
<prop>
<displayname>${this.displayname}</displayname>
${this.description ? `<CARD:addressbook-description>${this.description}</CARD:addressbook-description>` : ""}
<displayname>${escapeXml(this.displayname)}</displayname>
${this.description ? `<CARD:addressbook-description>${escapeXml(this.description)}</CARD:addressbook-description>` : ""}
</prop>
</set>
</mkcol>

View File

@@ -1,6 +1,6 @@
import { i, x } from "./lit-z6_uA4GX.mjs";
import { n as n$1, t } from "./property-D0NJdseG.mjs";
import { e, n } from "./ref-CPp9J0V5.mjs";
import { e, n, a as escapeXml } from "./index-b86iLJlP.mjs";
import { a as an } from "./webdav-D0R7xCzX.mjs";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -119,12 +119,12 @@ let CreateCalendarForm = class extends i {
<mkcol xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/" xmlns:ICAL="http://apple.com/ns/ical/">
<set>
<prop>
<displayname>${this.displayname}</displayname>
${this.description ? `<CAL:calendar-description>${this.description}</CAL:calendar-description>` : ""}
${this.color ? `<ICAL:calendar-color>${this.color}</ICAL:calendar-color>` : ""}
${this.isSubscription && this.subscriptionUrl ? `<CS:source><href>${this.subscriptionUrl}</href></CS:source>` : ""}
<displayname>${escapeXml(this.displayname)}</displayname>
${this.description ? `<CAL:calendar-description>${escapeXml(this.description)}</CAL:calendar-description>` : ""}
${this.color ? `<ICAL:calendar-color>${escapeXml(this.color)}</ICAL:calendar-color>` : ""}
${this.isSubscription && this.subscriptionUrl ? `<CS:source><href>${escapeXml(this.subscriptionUrl)}</href></CS:source>` : ""}
<CAL:supported-calendar-component-set>
${Array.from(this.components.keys()).map((comp) => `<CAL:comp name="${comp}" />`).join("\n")}
${Array.from(this.components.keys()).map((comp) => `<CAL:comp name="${escapeXml(comp)}" />`).join("\n")}
</CAL:supported-calendar-component-set>
</prop>
</set>

View File

@@ -122,7 +122,11 @@ const o = /* @__PURE__ */ new WeakMap(), n = e$1(class extends f {
this.rt(this.ct);
}
});
function escapeXml(unsafe) {
return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
}
export {
escapeXml as a,
e,
n
};

View File

@@ -205,10 +205,21 @@ ul.collection-list {
li.collection-list-item {
list-style: none;
display: contents;
display: block;
position: relative;
background: color-mix(in srgb, var(--background-color), var(--dilute-color) 5%);
border: 2px solid var(--border-color);
border-radius: 12px;
margin: 12px 0;
box-shadow: 4px 2px 12px -6px black;
overflow: hidden;
a {
background: color-mix(in srgb, var(--background-color), var(--dilute-color) 5%);
position: absolute;
inset: 2px;
}
.inner {
display: grid;
min-height: 80px;
height: fit-content;
@@ -227,11 +238,15 @@ ul.collection-list {
text-decoration: none;
padding-left: 12px;
border: 2px solid var(--border-color);
border-radius: 12px;
margin: 12px 0;
box-shadow: 4px 2px 12px -6px black;
overflow: hidden;
position: relative;
z-index: 1;
pointer-events: none;
a,
button {
pointer-events: all;
cursor: pointer;
}
.title {
font-weight: bold;
@@ -283,10 +298,10 @@ ul.collection-list {
display: flex;
gap: 12px;
}
}
&:hover {
background: color-mix(in srgb, var(--background-color), var(--dilute-color) 10%);
}
&:hover {
background: color-mix(in srgb, var(--background-color), var(--dilute-color) 10%);
}
}
}

View File

@@ -2,7 +2,8 @@
<ul class="collection-list">
{% for (meta, addressbook) in addressbooks %}
<li class="collection-list-item">
<a href="/frontend/user/{{ addressbook.principal }}/addressbook/{{ addressbook.id}}">
<a href="/frontend/user/{{ addressbook.principal }}/addressbook/{{ addressbook.id}}"></a>
<div class="inner">
<span class="title">
{%- if addressbook.principal != user.id -%}{{ addressbook.principal }}/{%- endif -%}
{{ addressbook.displayname.to_owned().unwrap_or(addressbook.id.to_owned()) }}
@@ -21,7 +22,7 @@
<div class="metadata">
{{ meta.len }} ({{ meta.size | filesizeformat }}) objects, {{ meta.deleted_len }} ({{ meta.deleted_size | filesizeformat }}) deleted objects
</div>
</a>
</div>
</li>
{% else %}
You do not have any addressbooks yet
@@ -32,7 +33,8 @@
<ul class="collection-list">
{% for (meta, addressbook) in deleted_addressbooks %}
<li class="collection-list-item">
<a href="/frontend/user/{{ addressbook.principal }}/addressbook/{{ addressbook.id}}">
<a href="/frontend/user/{{ addressbook.principal }}/addressbook/{{ addressbook.id}}"></a>
<div class="inner">
<span class="title">
{%- if addressbook.principal != user.id -%}{{ addressbook.principal }}/{%- endif -%}
{{ addressbook.displayname.to_owned().unwrap_or(addressbook.id.to_owned()) }}
@@ -50,7 +52,7 @@
<div class="metadata">
{{ meta.len }} ({{ meta.size | filesizeformat }}) objects, {{ meta.deleted_len }} ({{ meta.deleted_size | filesizeformat }}) deleted objects
</div>
</a>
</div>
</li>
{% endfor %}
</ul>

View File

@@ -3,7 +3,8 @@
{% for (meta, calendar) in calendars %}
{% let color = calendar.color.to_owned().unwrap_or("transparent".to_owned()) %}
<li class="collection-list-item" style="--color: {{ color }}">
<a href="/frontend/user/{{ calendar.principal }}/calendar/{{ calendar.id }}">
<a href="/frontend/user/{{ calendar.principal }}/calendar/{{ calendar.id }}"></a>
<div class="inner">
<span class="title">
{%- if calendar.principal != user.id -%}{{ calendar.principal }}/{%- endif -%}
{{ calendar.displayname.to_owned().unwrap_or(calendar.id.to_owned()) }}
@@ -31,7 +32,7 @@
{{ meta.len }} ({{ meta.size | filesizeformat }}) objects, {{ meta.deleted_len }} ({{ meta.deleted_size | filesizeformat }}) deleted objects
</div>
<div class="color-chip"></div>
</a>
</div>
</li>
{% else %}
You do not have any calendars yet
@@ -43,7 +44,8 @@
{% for (meta, calendar) in deleted_calendars %}
{% let color = calendar.color.to_owned().unwrap_or("transparent".to_owned()) %}
<li class="collection-list-item" style="--color: {{ color }}">
<a href="/frontend/user/{{ calendar.principal }}/calendar/{{ calendar.id}}">
<a href="/frontend/user/{{ calendar.principal }}/calendar/{{ calendar.id}}"></a>
<div class="inner">
<span class="title">
{%- if calendar.principal != user.id -%}{{ calendar.principal }}/{%- endif -%}
{{ calendar.displayname.to_owned().unwrap_or(calendar.id.to_owned()) }}
@@ -67,7 +69,7 @@
{{ meta.len }} ({{ meta.size | filesizeformat }}) objects, {{ meta.deleted_len }} ({{ meta.deleted_size | filesizeformat }}) deleted objects
</div>
<div class="color-chip"></div>
</a>
</div>
</li>
{% endfor %}
</ul>