mirror of
https://github.com/etesync/android
synced 2024-12-23 07:08:16 +00:00
Support for read-only calendars (closes #126)
* relevant RFCs go into the doc/ directory for reference purposes * read-only calendar collections are set as read-only in Android * HTTP exception refactoring to mark 4xx HTTP errors as hard sync errors (numAuthExcetions/numParseExceptions) for Android sync manager * query current-user-privilege-set for resources, detect read-only resources * show read-only resources as read-only in SelectCollectionsFragment * minor refactoring (DavProp.*)
This commit is contained in:
parent
70973dcc0a
commit
a12942c606
4035
doc/rfc3744-webdav-access-control-protocol.txt
Normal file
4035
doc/rfc3744-webdav-access-control-protocol.txt
Normal file
File diff suppressed because it is too large
Load Diff
5995
doc/rfc4791-caldav.txt
Normal file
5995
doc/rfc4791-caldav.txt
Normal file
File diff suppressed because it is too large
Load Diff
7115
doc/rfc4918-webdav.txt
Normal file
7115
doc/rfc4918-webdav.txt
Normal file
File diff suppressed because it is too large
Load Diff
281
doc/rfc5397-webdav-current-principal-extension.txt
Normal file
281
doc/rfc5397-webdav-current-principal-extension.txt
Normal file
@ -0,0 +1,281 @@
|
||||
|
||||
|
||||
|
||||
Network Working Group W. Sanchez
|
||||
Request for Comments: 5397 C. Daboo
|
||||
Category: Standards Track Apple Inc.
|
||||
December 2008
|
||||
|
||||
|
||||
WebDAV Current Principal Extension
|
||||
|
||||
Status of This Memo
|
||||
|
||||
This document specifies an Internet standards track protocol for the
|
||||
Internet community, and requests discussion and suggestions for
|
||||
improvements. Please refer to the current edition of the "Internet
|
||||
Official Protocol Standards" (STD 1) for the standardization state
|
||||
and status of this protocol. Distribution of this memo is unlimited.
|
||||
|
||||
Copyright Notice
|
||||
|
||||
Copyright (c) 2008 IETF Trust and the persons identified as the
|
||||
document authors. All rights reserved.
|
||||
|
||||
This document is subject to BCP 78 and the IETF Trust's Legal
|
||||
Provisions Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info) in effect on the date of
|
||||
publication of this document. Please review these documents
|
||||
carefully, as they describe your rights and restrictions with respect
|
||||
to this document.
|
||||
|
||||
Abstract
|
||||
|
||||
This specification defines a new WebDAV property that allows clients
|
||||
to quickly determine the principal corresponding to the current
|
||||
authenticated user.
|
||||
|
||||
Table of Contents
|
||||
|
||||
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 2
|
||||
2. Conventions Used in This Document . . . . . . . . . . . . . . . 2
|
||||
3. DAV:current-user-principal . . . . . . . . . . . . . . . . . . 3
|
||||
4. Security Considerations . . . . . . . . . . . . . . . . . . . . 4
|
||||
5. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . 4
|
||||
6. Normative References . . . . . . . . . . . . . . . . . . . . . 4
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Sanchez & Daboo Standards Track [Page 1]
|
||||
|
||||
RFC 5397 WebDAV Current Principal December 2008
|
||||
|
||||
|
||||
1. Introduction
|
||||
|
||||
WebDAV [RFC4918] is an extension to HTTP [RFC2616] to support
|
||||
improved document authoring capabilities. The WebDAV Access Control
|
||||
Protocol ("WebDAV ACL") [RFC3744] extension adds access control
|
||||
capabilities to WebDAV. It introduces the concept of a "principal"
|
||||
resource, which is used to represent information about authenticated
|
||||
entities on the system.
|
||||
|
||||
Some clients have a need to determine which [RFC3744] principal a
|
||||
server is associating with the currently authenticated HTTP user.
|
||||
While [RFC3744] defines a DAV:current-user-privilege-set property for
|
||||
retrieving the privileges granted to that principal, there is no
|
||||
recommended way to identify the principal in question, which is
|
||||
necessary to perform other useful operations. For example, a client
|
||||
may wish to determine which groups the current user is a member of,
|
||||
or modify a property of the principal resource associated with the
|
||||
current user.
|
||||
|
||||
The DAV:principal-match REPORT provides some useful functionality,
|
||||
but there are common situations where the results from that query can
|
||||
be ambiguous. For example, not only is an individual user principal
|
||||
returned, but also every group principal that the user is a member
|
||||
of, and there is no clear way to distinguish which is which.
|
||||
|
||||
This specification proposes an extension to WebDAV ACL that adds a
|
||||
DAV:current-user-principal property to resources under access control
|
||||
on the server. This property provides a URL to a principal resource
|
||||
corresponding to the currently authenticated user. This allows a
|
||||
client to "bootstrap" itself by performing additional queries on the
|
||||
principal resource to obtain additional information from that
|
||||
resource, which is the purpose of this extension. Note that while it
|
||||
is possible for multiple URLs to refer to the same principal
|
||||
resource, or for multiple principal resources to correspond to a
|
||||
single principal, this specification only allows for a single http(s)
|
||||
URL in the DAV:current-user-principal property. If a client wishes
|
||||
to obtain alternate URLs for the principal, it can query the
|
||||
principal resource for this information; it is not the purpose of
|
||||
this extension to provide a complete list of such URLs, but simply to
|
||||
provide a means to locate a resource which contains that (and other)
|
||||
information.
|
||||
|
||||
2. Conventions Used in This Document
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
||||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
|
||||
document are to be interpreted as described in [RFC2119].
|
||||
|
||||
|
||||
|
||||
|
||||
Sanchez & Daboo Standards Track [Page 2]
|
||||
|
||||
RFC 5397 WebDAV Current Principal December 2008
|
||||
|
||||
|
||||
When XML element types in the namespace "DAV:" are referenced in this
|
||||
document outside of the context of an XML fragment, the string "DAV:"
|
||||
will be prefixed to the element type names.
|
||||
|
||||
Processing of XML by clients and servers MUST follow the rules
|
||||
defined in Section 17 of WebDAV [RFC4918].
|
||||
|
||||
Some of the declarations refer to XML elements defined by WebDAV
|
||||
[RFC4918].
|
||||
|
||||
3. DAV:current-user-principal
|
||||
|
||||
Name: current-user-principal
|
||||
|
||||
Namespace: DAV:
|
||||
|
||||
Purpose: Indicates a URL for the currently authenticated user's
|
||||
principal resource on the server.
|
||||
|
||||
Value: A single DAV:href or DAV:unauthenticated element.
|
||||
|
||||
Protected: This property is computed on a per-request basis, and
|
||||
therefore is protected.
|
||||
|
||||
Description: The DAV:current-user-principal property contains either
|
||||
a DAV:href or DAV:unauthenticated XML element. The DAV:href
|
||||
element contains a URL to a principal resource corresponding to
|
||||
the currently authenticated user. That URL MUST be one of the
|
||||
URLs in the DAV:principal-URL or DAV:alternate-URI-set properties
|
||||
defined on the principal resource and MUST be an http(s) scheme
|
||||
URL. When authentication has not been done or has failed, this
|
||||
property MUST contain the DAV:unauthenticated pseudo-principal.
|
||||
|
||||
In some cases, there may be multiple principal resources
|
||||
corresponding to the same authenticated principal. In that case,
|
||||
the server is free to choose any one of the principal resource
|
||||
URIs for the value of the DAV:current-user-principal property.
|
||||
However, servers SHOULD be consistent and use the same principal
|
||||
resource URI for each authenticated principal.
|
||||
|
||||
COPY/MOVE behavior: This property is computed on a per-request
|
||||
basis, and is thus never copied or moved.
|
||||
|
||||
Definition:
|
||||
|
||||
<!ELEMENT current-user-principal (unauthenticated | href)>
|
||||
<!-- href value: a URL to a principal resource -->
|
||||
|
||||
|
||||
|
||||
|
||||
Sanchez & Daboo Standards Track [Page 3]
|
||||
|
||||
RFC 5397 WebDAV Current Principal December 2008
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
<D:current-user-principal xmlns:D="DAV:">
|
||||
<D:href>/principals/users/cdaboo</D:href>
|
||||
</D:current-user-principal>
|
||||
|
||||
4. Security Considerations
|
||||
|
||||
This specification does not introduce any additional security issues
|
||||
beyond those defined for HTTP [RFC2616], WebDAV [RFC4918], and WebDAV
|
||||
ACL [RFC3744].
|
||||
|
||||
5. Acknowledgments
|
||||
|
||||
This specification is based on discussions that took place within the
|
||||
Calendaring and Scheduling Consortium's CalDAV Technical Committee.
|
||||
The authors thank the participants of that group for their input.
|
||||
|
||||
The authors thank Julian Reschke for his valuable input via the
|
||||
WebDAV working group mailing list.
|
||||
|
||||
6. Normative References
|
||||
|
||||
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
|
||||
Requirement Levels", BCP 14, RFC 2119, March 1997.
|
||||
|
||||
[RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H.,
|
||||
Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext
|
||||
Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
|
||||
|
||||
[RFC3744] Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web
|
||||
Distributed Authoring and Versioning (WebDAV)
|
||||
Access Control Protocol", RFC 3744, May 2004.
|
||||
|
||||
[RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed
|
||||
Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
|
||||
|
||||
Authors' Addresses
|
||||
|
||||
Wilfredo Sanchez
|
||||
Apple Inc.
|
||||
1 Infinite Loop
|
||||
Cupertino, CA 95014
|
||||
USA
|
||||
|
||||
EMail: wsanchez@wsanchez.net
|
||||
URI: http://www.apple.com/
|
||||
|
||||
|
||||
|
||||
|
||||
Sanchez & Daboo Standards Track [Page 4]
|
||||
|
||||
RFC 5397 WebDAV Current Principal December 2008
|
||||
|
||||
|
||||
Cyrus Daboo
|
||||
Apple Inc.
|
||||
1 Infinite Loop
|
||||
Cupertino, CA 95014
|
||||
USA
|
||||
|
||||
EMail: cyrus@daboo.name
|
||||
URI: http://www.apple.com/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Sanchez & Daboo Standards Track [Page 5]
|
||||
|
||||
|
2691
doc/rfc6352-carddav.txt
Normal file
2691
doc/rfc6352-carddav.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -83,6 +83,7 @@
|
||||
<string name="account_name_hint">Mein CalDAV/CardDAV-Konto</string>
|
||||
<string name="email_address">Email-Adresse:</string>
|
||||
<string name="organizer_hint">"ORGANIZER der von Ihnen angelegten Termine; notwendig für Teilnehmer-Info"</string>
|
||||
<string name="account_name_info">"Verwenden Sie Ihre Email-Addresse als Kontoname, da Android den Kontonamen als ORGANIZER-Feld in Terminen benutzt. Sie können keine zwei Konten mit dem gleichen Namen anlegen.</string>
|
||||
<string name="account_name_info">"Verwenden Sie Ihre Email-Adresse als Kontoname, da Android den Kontonamen als ORGANIZER-Feld in Terminen benutzt. Sie können keine zwei Konten mit dem gleichen Namen anlegen.</string>
|
||||
<string name="read_only">schreibgeschützt</string>
|
||||
|
||||
</resources>
|
@ -92,5 +92,6 @@
|
||||
<string name="email_address">Email address:</string>
|
||||
<string name="organizer_hint">"ORGANIZER of your events; required if you use attendee info"</string>
|
||||
<string name="account_name_info">"Use your email address as account name because Android will use the account name as ORGANIZER field for events you create. You can't have two accounts with the same name.</string>
|
||||
<string name="read_only">read-only</string>
|
||||
|
||||
</resources>
|
||||
|
@ -122,13 +122,18 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
values.put(Calendars.NAME, info.getPath());
|
||||
values.put(Calendars.CALENDAR_DISPLAY_NAME, info.getTitle());
|
||||
values.put(Calendars.CALENDAR_COLOR, color);
|
||||
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER);
|
||||
values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT);
|
||||
values.put(Calendars.CAN_ORGANIZER_RESPOND, 1);
|
||||
values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1);
|
||||
values.put(Calendars.OWNER_ACCOUNT, account.name);
|
||||
values.put(Calendars.SYNC_EVENTS, 1);
|
||||
values.put(Calendars.VISIBLE, 1);
|
||||
values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT);
|
||||
|
||||
if (info.isReadOnly())
|
||||
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ);
|
||||
else {
|
||||
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER);
|
||||
values.put(Calendars.CAN_ORGANIZER_RESPOND, 1);
|
||||
values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1);
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 15) {
|
||||
values.put(Calendars.ALLOWED_AVAILABILITY, Events.AVAILABILITY_BUSY + "," + Events.AVAILABILITY_FREE + "," + Events.AVAILABILITY_TENTATIVE);
|
||||
|
@ -22,13 +22,11 @@ import java.util.List;
|
||||
import lombok.Cleanup;
|
||||
import lombok.Getter;
|
||||
import net.fortuna.ical4j.model.ValidationException;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
|
||||
import android.util.Log;
|
||||
import at.bitfire.davdroid.webdav.DavException;
|
||||
import at.bitfire.davdroid.webdav.DavMultiget;
|
||||
import at.bitfire.davdroid.webdav.DavNoContentException;
|
||||
import at.bitfire.davdroid.webdav.HttpException;
|
||||
import at.bitfire.davdroid.webdav.HttpPropfind;
|
||||
import at.bitfire.davdroid.webdav.WebDavResource;
|
||||
import at.bitfire.davdroid.webdav.WebDavResource.PutMode;
|
||||
@ -112,7 +110,7 @@ public abstract class RemoteCollection<T extends Resource> {
|
||||
|
||||
/* internal member operations */
|
||||
|
||||
public Resource get(Resource resource) throws IOException, HttpException, InvalidResourceException {
|
||||
public Resource get(Resource resource) throws IOException, HttpException, DavException, InvalidResourceException {
|
||||
WebDavResource member = new WebDavResource(collection, resource.getName());
|
||||
member.get();
|
||||
|
||||
|
@ -13,11 +13,9 @@ package at.bitfire.davdroid.syncadapter;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.auth.AuthenticationException;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
@ -32,10 +30,12 @@ import at.bitfire.davdroid.resource.LocalCollection;
|
||||
import at.bitfire.davdroid.resource.LocalStorageException;
|
||||
import at.bitfire.davdroid.resource.RemoteCollection;
|
||||
import at.bitfire.davdroid.webdav.DavException;
|
||||
import at.bitfire.davdroid.webdav.HttpException;
|
||||
|
||||
public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
private final static String TAG = "davdroid.DavSyncAdapter";
|
||||
|
||||
protected Context context;
|
||||
protected AccountManager accountManager;
|
||||
|
||||
@Getter private static String androidID;
|
||||
@ -48,7 +48,8 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
if (androidID == null)
|
||||
androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
}
|
||||
|
||||
|
||||
this.context = context;
|
||||
accountManager = AccountManager.get(context);
|
||||
}
|
||||
|
||||
@ -70,15 +71,22 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
for (Map.Entry<LocalCollection<?>, RemoteCollection<?>> entry : syncCollections.entrySet())
|
||||
new SyncManager(entry.getKey(), entry.getValue()).synchronize(extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL), syncResult);
|
||||
|
||||
} catch (AuthenticationException ex) {
|
||||
syncResult.stats.numAuthExceptions++;
|
||||
Log.e(TAG, "HTTP authentication failed", ex);
|
||||
} catch (DavException ex) {
|
||||
syncResult.stats.numParseExceptions++;
|
||||
Log.e(TAG, "Invalid DAV response", ex);
|
||||
|
||||
} catch (HttpException ex) {
|
||||
syncResult.stats.numIoExceptions++;
|
||||
Log.e(TAG, "HTTP error", ex);
|
||||
if (ex.getCode() == HttpStatus.SC_UNAUTHORIZED) {
|
||||
Log.e(TAG, "HTTP Unauthorized " + ex.getCode(), ex);
|
||||
syncResult.stats.numAuthExceptions++;
|
||||
} else if (ex.isClientError()) {
|
||||
Log.e(TAG, "Hard HTTP error " + ex.getCode(), ex);
|
||||
syncResult.stats.numParseExceptions++;
|
||||
} else {
|
||||
Log.w(TAG, "Soft HTTP error" + ex.getCode(), ex);
|
||||
syncResult.stats.numIoExceptions++;
|
||||
}
|
||||
|
||||
} catch (LocalStorageException ex) {
|
||||
syncResult.databaseError = true;
|
||||
Log.e(TAG, "Local storage (content provider) exception", ex);
|
||||
|
@ -31,6 +31,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.webdav.DavException;
|
||||
import at.bitfire.davdroid.webdav.HttpPropfind.Mode;
|
||||
import at.bitfire.davdroid.webdav.DavIncapableException;
|
||||
import at.bitfire.davdroid.webdav.WebDavResource;
|
||||
@ -165,6 +166,7 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
|
||||
Log.i(TAG, "Found address book: " + resource.getLocation().getRawPath());
|
||||
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
|
||||
ServerInfo.ResourceInfo.Type.ADDRESS_BOOK,
|
||||
resource.isReadOnly(),
|
||||
resource.getLocation().getRawPath(),
|
||||
resource.getDisplayName(),
|
||||
resource.getDescription(), resource.getColor()
|
||||
@ -196,6 +198,7 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
|
||||
}
|
||||
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
|
||||
ServerInfo.ResourceInfo.Type.CALENDAR,
|
||||
resource.isReadOnly(),
|
||||
resource.getLocation().getRawPath(),
|
||||
resource.getDisplayName(),
|
||||
resource.getDescription(), resource.getColor()
|
||||
@ -211,9 +214,11 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
|
||||
serverInfo.setErrorMessage(getContext().getString(R.string.exception_uri_syntax, e.getMessage()));
|
||||
} catch (IOException e) {
|
||||
serverInfo.setErrorMessage(getContext().getString(R.string.exception_io, e.getLocalizedMessage()));
|
||||
} catch (DavIncapableException e) {
|
||||
} catch (DavException e) {
|
||||
Log.e(TAG, "DAV error while querying server info", e);
|
||||
serverInfo.setErrorMessage(getContext().getString(R.string.exception_incapable_resource, e.getLocalizedMessage()));
|
||||
} catch (HttpException e) {
|
||||
Log.e(TAG, "HTTP error while querying server info", e);
|
||||
serverInfo.setErrorMessage(getContext().getString(R.string.exception_http, e.getLocalizedMessage()));
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import lombok.Getter;
|
||||
import android.content.Context;
|
||||
import android.text.Html;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -26,11 +27,14 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
|
||||
TYPE_CALENDARS_HEADING = 2,
|
||||
TYPE_CALENDARS_ROW = 3;
|
||||
|
||||
protected Context context;
|
||||
protected ServerInfo serverInfo;
|
||||
@Getter protected int nAddressBooks, nCalendars;
|
||||
|
||||
|
||||
public SelectCollectionsAdapter(ServerInfo serverInfo) {
|
||||
public SelectCollectionsAdapter(Context context, ServerInfo serverInfo) {
|
||||
this.context = context;
|
||||
|
||||
this.serverInfo = serverInfo;
|
||||
nAddressBooks = (serverInfo.getAddressBooks() == null) ? 0 : serverInfo.getAddressBooks().size();
|
||||
nCalendars = (serverInfo.getCalendars() == null) ? 0 : serverInfo.getCalendars().size();
|
||||
@ -130,8 +134,12 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter
|
||||
if (description == null)
|
||||
description = info.getPath();
|
||||
|
||||
String title = "<b>" + info.getTitle() + "</b>";
|
||||
if (info.isReadOnly())
|
||||
title = title + " (" + context.getString(R.string.read_only) + ")";
|
||||
|
||||
// FIXME escape HTML
|
||||
view.setText(Html.fromHtml("<b>" + info.getTitle() + "</b><br/>" + description));
|
||||
view.setText(Html.fromHtml(title + "<br/>" + description));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,7 +51,7 @@ public class SelectCollectionsFragment extends ListFragment {
|
||||
listView.addHeaderView(header);
|
||||
|
||||
final ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO);
|
||||
final SelectCollectionsAdapter adapter = new SelectCollectionsAdapter(serverInfo);
|
||||
final SelectCollectionsAdapter adapter = new SelectCollectionsAdapter(view.getContext(), serverInfo);
|
||||
setListAdapter(adapter);
|
||||
|
||||
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
|
@ -54,6 +54,7 @@ public class ServerInfo implements Serializable {
|
||||
boolean enabled = false;
|
||||
|
||||
final Type type;
|
||||
final boolean readOnly;
|
||||
final String path, title, description, color;
|
||||
|
||||
String timezone;
|
||||
|
@ -12,9 +12,6 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.fortuna.ical4j.model.ValidationException;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
|
||||
import android.content.SyncResult;
|
||||
import android.util.Log;
|
||||
import at.bitfire.davdroid.ArrayUtils;
|
||||
@ -23,6 +20,8 @@ import at.bitfire.davdroid.resource.LocalStorageException;
|
||||
import at.bitfire.davdroid.resource.RecordNotFoundException;
|
||||
import at.bitfire.davdroid.resource.RemoteCollection;
|
||||
import at.bitfire.davdroid.resource.Resource;
|
||||
import at.bitfire.davdroid.webdav.DavException;
|
||||
import at.bitfire.davdroid.webdav.HttpException;
|
||||
import at.bitfire.davdroid.webdav.NotFoundException;
|
||||
import at.bitfire.davdroid.webdav.PreconditionFailedException;
|
||||
|
||||
@ -41,7 +40,7 @@ public class SyncManager {
|
||||
}
|
||||
|
||||
|
||||
public void synchronize(boolean manualSync, SyncResult syncResult) throws LocalStorageException, IOException, HttpException {
|
||||
public void synchronize(boolean manualSync, SyncResult syncResult) throws LocalStorageException, IOException, HttpException, DavException {
|
||||
// PHASE 1: push local changes to server
|
||||
int deletedRemotely = pushDeleted(),
|
||||
addedRemotely = pushNew(),
|
||||
@ -175,7 +174,7 @@ public class SyncManager {
|
||||
return count;
|
||||
}
|
||||
|
||||
private int pullNew(Resource[] resourcesToAdd) throws LocalStorageException, IOException, HttpException {
|
||||
private int pullNew(Resource[] resourcesToAdd) throws LocalStorageException, IOException, HttpException, DavException {
|
||||
int count = 0;
|
||||
Log.i(TAG, "Fetching " + resourcesToAdd.length + " new remote resource(s)");
|
||||
|
||||
@ -189,7 +188,7 @@ public class SyncManager {
|
||||
return count;
|
||||
}
|
||||
|
||||
private int pullChanged(Resource[] resourcesToUpdate) throws LocalStorageException, IOException, HttpException {
|
||||
private int pullChanged(Resource[] resourcesToUpdate) throws LocalStorageException, IOException, HttpException, DavException {
|
||||
int count = 0;
|
||||
Log.i(TAG, "Fetching " + resourcesToUpdate.length + " updated remote resource(s)");
|
||||
|
||||
|
@ -10,13 +10,13 @@
|
||||
******************************************************************************/
|
||||
package at.bitfire.davdroid.webdav;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
|
||||
public class DavException extends HttpException {
|
||||
public class DavException extends Exception {
|
||||
private static final long serialVersionUID = -2118919144443165706L;
|
||||
|
||||
final private static String prefix = "Invalid DAV response: ";
|
||||
|
||||
/* used to indiciate DAV protocol errors */
|
||||
|
||||
|
||||
public DavException(String message) {
|
||||
super(prefix + message);
|
||||
|
@ -13,6 +13,8 @@ package at.bitfire.davdroid.webdav;
|
||||
public class DavIncapableException extends DavException {
|
||||
private static final long serialVersionUID = -7199786680939975667L;
|
||||
|
||||
/* used to indicate that the server doesn't support DAV */
|
||||
|
||||
public DavIncapableException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
@ -24,14 +24,8 @@ import org.simpleframework.xml.Text;
|
||||
@Namespace(prefix="D",reference="DAV:")
|
||||
@Root(strict=false)
|
||||
public class DavProp {
|
||||
@Element(required=false,name="current-user-principal")
|
||||
DavCurrentUserPrincipal currentUserPrincipal;
|
||||
|
||||
@Element(required=false,name="addressbook-home-set")
|
||||
DavAddressbookHomeSet addressbookHomeSet;
|
||||
|
||||
@Element(required=false,name="calendar-home-set")
|
||||
DavCalendarHomeSet calendarHomeSet;
|
||||
/* RFC 4918 WebDAV */
|
||||
|
||||
@Element(required=false)
|
||||
DavPropResourceType resourcetype;
|
||||
@ -39,6 +33,80 @@ public class DavProp {
|
||||
@Element(required=false)
|
||||
DavPropDisplayName displayname;
|
||||
|
||||
@Element(required=false)
|
||||
DavPropGetCTag getctag;
|
||||
|
||||
@Element(required=false)
|
||||
DavPropGetETag getetag;
|
||||
|
||||
@Root(strict=false)
|
||||
public static class DavPropResourceType {
|
||||
@Element(required=false)
|
||||
@Getter private Addressbook addressbook;
|
||||
@Element(required=false)
|
||||
@Getter private Calendar calendar;
|
||||
|
||||
@Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav")
|
||||
public static class Addressbook { }
|
||||
|
||||
@Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav")
|
||||
public static class Calendar { }
|
||||
}
|
||||
|
||||
public static class DavPropDisplayName {
|
||||
@Text(required=false)
|
||||
@Getter private String displayName;
|
||||
}
|
||||
|
||||
@Namespace(prefix="CS",reference="http://calendarserver.org/ns/")
|
||||
public static class DavPropGetCTag {
|
||||
@Text(required=false)
|
||||
@Getter private String CTag;
|
||||
}
|
||||
|
||||
public static class DavPropGetETag {
|
||||
@Text(required=false)
|
||||
@Getter private String ETag;
|
||||
}
|
||||
|
||||
|
||||
/* RFC 5397 WebDAV Current Principal Extension */
|
||||
|
||||
@Element(required=false,name="current-user-principal")
|
||||
DavCurrentUserPrincipal currentUserPrincipal;
|
||||
|
||||
public static class DavCurrentUserPrincipal {
|
||||
@Element(required=false)
|
||||
@Getter private DavHref href;
|
||||
}
|
||||
|
||||
|
||||
/* RFC 3744 WebDAV Access Control Protocol */
|
||||
|
||||
@ElementList(required=false,name="current-user-privilege-set",entry="privilege")
|
||||
List<DavPropPrivilege> currentUserPrivilegeSet;
|
||||
|
||||
public static class DavPropPrivilege {
|
||||
@Element(required=false)
|
||||
@Getter private PrivAll all;
|
||||
|
||||
@Element(required=false)
|
||||
@Getter private PrivWrite write;
|
||||
|
||||
public static class PrivAll { }
|
||||
public static class PrivWrite { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* RFC 4791 CalDAV, RFC 6352 CardDAV */
|
||||
|
||||
@Element(required=false,name="addressbook-home-set")
|
||||
DavAddressbookHomeSet addressbookHomeSet;
|
||||
|
||||
@Element(required=false,name="calendar-home-set")
|
||||
DavCalendarHomeSet calendarHomeSet;
|
||||
|
||||
@Element(required=false,name="addressbook-description")
|
||||
DavPropAddressbookDescription addressbookDescription;
|
||||
|
||||
@ -51,14 +119,9 @@ public class DavProp {
|
||||
@Element(required=false,name="calendar-timezone")
|
||||
DavPropCalendarTimezone calendarTimezone;
|
||||
|
||||
@Element(required=false,name="supported-calendar-component-set")
|
||||
DavPropSupportedCalendarComponentSet supportedCalendarComponentSet;
|
||||
|
||||
@Element(required=false)
|
||||
DavPropGetCTag getctag;
|
||||
|
||||
@Element(required=false)
|
||||
DavPropGetETag getetag;
|
||||
@Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav")
|
||||
@ElementList(required=false,name="supported-calendar-component-set",entry="comp")
|
||||
List<DavPropComp> supportedCalendarComponentSet;
|
||||
|
||||
@Element(name="address-data",required=false)
|
||||
DavPropAddressData addressData;
|
||||
@ -66,11 +129,6 @@ public class DavProp {
|
||||
@Element(name="calendar-data",required=false)
|
||||
DavPropCalendarData calendarData;
|
||||
|
||||
|
||||
public static class DavCurrentUserPrincipal {
|
||||
@Element(required=false)
|
||||
@Getter private DavHref href;
|
||||
}
|
||||
|
||||
@Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav")
|
||||
public static class DavAddressbookHomeSet {
|
||||
@ -83,25 +141,6 @@ public class DavProp {
|
||||
@Element(required=false)
|
||||
@Getter private DavHref href;
|
||||
}
|
||||
|
||||
@Root(strict=false)
|
||||
public static class DavPropResourceType {
|
||||
@Element(required=false)
|
||||
@Getter private DavAddressbook addressbook;
|
||||
@Element(required=false)
|
||||
@Getter private DavCalendar calendar;
|
||||
|
||||
@Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav")
|
||||
public static class DavAddressbook { }
|
||||
|
||||
@Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav")
|
||||
public static class DavCalendar { }
|
||||
}
|
||||
|
||||
public static class DavPropDisplayName {
|
||||
@Text(required=false)
|
||||
@Getter private String displayName;
|
||||
}
|
||||
|
||||
@Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav")
|
||||
public static class DavPropAddressbookDescription {
|
||||
@ -127,28 +166,11 @@ public class DavProp {
|
||||
@Getter private String timezone;
|
||||
}
|
||||
|
||||
@Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav")
|
||||
public static class DavPropSupportedCalendarComponentSet {
|
||||
@ElementList(inline=true,entry="comp",required=false)
|
||||
List<DavPropComp> components;
|
||||
}
|
||||
|
||||
@Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav")
|
||||
public static class DavPropComp {
|
||||
@Attribute
|
||||
@Getter String name;
|
||||
}
|
||||
|
||||
@Namespace(prefix="CS",reference="http://calendarserver.org/ns/")
|
||||
public static class DavPropGetCTag {
|
||||
@Text(required=false)
|
||||
@Getter private String CTag;
|
||||
}
|
||||
|
||||
public static class DavPropGetETag {
|
||||
@Text(required=false)
|
||||
@Getter private String ETag;
|
||||
}
|
||||
|
||||
@Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav")
|
||||
public static class DavPropAddressData {
|
||||
|
19
src/at/bitfire/davdroid/webdav/HttpException.java
Normal file
19
src/at/bitfire/davdroid/webdav/HttpException.java
Normal file
@ -0,0 +1,19 @@
|
||||
package at.bitfire.davdroid.webdav;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class HttpException extends org.apache.http.HttpException {
|
||||
private static final long serialVersionUID = -4805778240079377401L;
|
||||
|
||||
@Getter private int code;
|
||||
|
||||
HttpException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public boolean isClientError() {
|
||||
return code/100 == 4;
|
||||
}
|
||||
|
||||
}
|
@ -12,6 +12,7 @@ package at.bitfire.davdroid.webdav;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.net.URI;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
@ -52,11 +53,12 @@ public class HttpPropfind extends HttpEntityEnclosingRequestBase {
|
||||
depth = 1;
|
||||
propfind.prop.displayname = new DavProp.DavPropDisplayName();
|
||||
propfind.prop.resourcetype = new DavProp.DavPropResourceType();
|
||||
propfind.prop.currentUserPrivilegeSet = new LinkedList<DavProp.DavPropPrivilege>();
|
||||
propfind.prop.addressbookDescription = new DavProp.DavPropAddressbookDescription();
|
||||
propfind.prop.calendarDescription = new DavProp.DavPropCalendarDescription();
|
||||
propfind.prop.calendarColor = new DavProp.DavPropCalendarColor();
|
||||
propfind.prop.calendarTimezone = new DavProp.DavPropCalendarTimezone();
|
||||
propfind.prop.supportedCalendarComponentSet = new DavProp.DavPropSupportedCalendarComponentSet();
|
||||
propfind.prop.supportedCalendarComponentSet = new LinkedList<DavProp.DavPropComp>();
|
||||
break;
|
||||
case COLLECTION_CTAG:
|
||||
propfind.prop.getctag = new DavProp.DavPropGetCTag();
|
||||
|
@ -10,12 +10,12 @@
|
||||
******************************************************************************/
|
||||
package at.bitfire.davdroid.webdav;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpStatus;
|
||||
|
||||
public class NotFoundException extends HttpException {
|
||||
private static final long serialVersionUID = 1565961502781880483L;
|
||||
|
||||
public NotFoundException(String reason) {
|
||||
super(reason);
|
||||
super(HttpStatus.SC_NOT_FOUND, reason);
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,12 @@
|
||||
******************************************************************************/
|
||||
package at.bitfire.davdroid.webdav;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpStatus;
|
||||
|
||||
public class PreconditionFailedException extends HttpException {
|
||||
private static final long serialVersionUID = 102282229174086113L;
|
||||
|
||||
public PreconditionFailedException(String reason) {
|
||||
super(reason);
|
||||
super(HttpStatus.SC_PRECONDITION_FAILED, reason);
|
||||
}
|
||||
}
|
||||
|
@ -30,12 +30,10 @@ import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.AuthenticationException;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
@ -61,6 +59,7 @@ public class WebDavResource {
|
||||
|
||||
public enum Property {
|
||||
CURRENT_USER_PRINCIPAL,
|
||||
READ_ONLY,
|
||||
DISPLAY_NAME, DESCRIPTION, COLOR,
|
||||
TIMEZONE, SUPPORTED_COMPONENTS,
|
||||
ADDRESSBOOK_HOMESET, CALENDAR_HOMESET,
|
||||
@ -177,6 +176,10 @@ public class WebDavResource {
|
||||
return properties.get(Property.CURRENT_USER_PRINCIPAL);
|
||||
}
|
||||
|
||||
public boolean isReadOnly() {
|
||||
return properties.containsKey(Property.READ_ONLY);
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return properties.get(Property.DISPLAY_NAME);
|
||||
}
|
||||
@ -301,7 +304,7 @@ public class WebDavResource {
|
||||
|
||||
/* resource operations */
|
||||
|
||||
public void get() throws IOException, HttpException {
|
||||
public void get() throws IOException, HttpException, DavException {
|
||||
HttpGet get = new HttpGet(location);
|
||||
HttpResponse response = client.execute(get);
|
||||
checkResponse(response);
|
||||
@ -370,18 +373,16 @@ public class WebDavResource {
|
||||
|
||||
String reason = code + " " + statusLine.getReasonPhrase();
|
||||
switch (code) {
|
||||
case HttpStatus.SC_UNAUTHORIZED:
|
||||
throw new AuthenticationException(reason);
|
||||
case HttpStatus.SC_NOT_FOUND:
|
||||
throw new NotFoundException(reason);
|
||||
case HttpStatus.SC_PRECONDITION_FAILED:
|
||||
throw new PreconditionFailedException(reason);
|
||||
default:
|
||||
throw new HttpException(reason);
|
||||
throw new HttpException(code, reason);
|
||||
}
|
||||
}
|
||||
|
||||
protected void processMultiStatus(DavMultistatus multistatus) throws HttpException {
|
||||
protected void processMultiStatus(DavMultistatus multistatus) throws HttpException, DavException {
|
||||
if (multistatus.response == null) // empty response
|
||||
throw new DavNoContentException();
|
||||
|
||||
@ -421,6 +422,16 @@ public class WebDavResource {
|
||||
if (prop.currentUserPrincipal != null && prop.currentUserPrincipal.getHref() != null)
|
||||
properties.put(Property.CURRENT_USER_PRINCIPAL, prop.currentUserPrincipal.getHref().href);
|
||||
|
||||
if (prop.currentUserPrivilegeSet != null) {
|
||||
// privilege info available
|
||||
boolean hasWrite = false;
|
||||
for (DavProp.DavPropPrivilege privilege : prop.currentUserPrivilegeSet) {
|
||||
if (privilege.getAll() != null || privilege.getWrite() != null)
|
||||
hasWrite = true;
|
||||
}
|
||||
if (!hasWrite) properties.put(Property.READ_ONLY, "1");
|
||||
}
|
||||
|
||||
if (prop.addressbookHomeSet != null && prop.addressbookHomeSet.getHref() != null)
|
||||
properties.put(Property.ADDRESSBOOK_HOMESET, prop.addressbookHomeSet.getHref().href);
|
||||
|
||||
@ -449,9 +460,9 @@ public class WebDavResource {
|
||||
if (prop.calendarTimezone != null)
|
||||
properties.put(Property.TIMEZONE, Event.TimezoneDefToTzId(prop.calendarTimezone.getTimezone()));
|
||||
|
||||
if (prop.supportedCalendarComponentSet != null && prop.supportedCalendarComponentSet.components != null) {
|
||||
if (prop.supportedCalendarComponentSet != null) {
|
||||
referenced.supportedComponents = new LinkedList<String>();
|
||||
for (DavPropComp component : prop.supportedCalendarComponentSet.components)
|
||||
for (DavPropComp component : prop.supportedCalendarComponentSet)
|
||||
referenced.supportedComponents.add(component.getName());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user