summaryrefslogtreecommitdiffstats
path: root/comm/calendar/base/modules/utils/calProviderDetectionUtils.jsm
blob: ce76dbdd015d2ed0e39c50658fd619c16dfea096 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const EXPORTED_SYMBOLS = ["calproviderdetection"];

var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");

/**
 * Code to call the calendar provider detection mechanism.
 */

// NOTE: This module should not be loaded directly, it is available when
// including calUtils.jsm under the cal.provider.detection namespace.

/**
 * The base class marker for detection errors. Useful in instanceof checks.
 */
class DetectionError extends Error {}

/**
 * Creates an error class that extends the base detection error.
 *
 * @param {string} aName - The name of the constructor, used for the base error class.
 * @returns {DetectionError} A class extending DetectionError.
 */
function DetectionErrorClass(aName) {
  return class extends DetectionError {
    constructor(message) {
      super(message);
      this.name = aName;
    }
  };
}

/**
 * The exported `calproviderdetection` object.
 */
var calproviderdetection = {
  /**
   * A map of providers that implement detection. Maps the type identifier
   * (e.g. "ics", "caldav") to the provider object.
   *
   * @type {Map<string, calICalendarProvider>}
   */
  get providers() {
    let providers = new Map();
    for (let [type, provider] of cal.provider.providers) {
      if (provider.detectCalendars) {
        providers.set(type, provider);
      }
    }
    return providers;
  },

  /**
   * Known domains for Google OAuth. This is just to catch the most common case,
   * MX entries should be checked for remaining cases.
   *
   * @type {Set<string>}
   */
  googleOAuthDomains: new Set(["gmail.com", "googlemail.com", "apidata.googleusercontent.com"]),

  /**
   * Translate location and username to an uri. If the location is empty, the
   * domain part of the username is taken. If the location is a hostname it is
   * converted to a https:// uri, if it is an uri string then use that.
   *
   * @param {string} aLocation - The location string.
   * @param {string} aUsername - The username string.
   * @returns {nsIURI} The resulting location uri.
   */
  locationToUri(aLocation, aUsername) {
    let uri = null;
    if (!aLocation) {
      let match = aUsername.match(/[^@]+@([^.]+\..*)/);
      if (match) {
        uri = Services.io.newURI("https://" + match[1]);
      }
    } else if (aLocation.includes("://")) {
      // Try to parse it as an uri
      uri = Services.io.newURI(aLocation);
    } else {
      // Maybe its just a simple hostname
      uri = Services.io.newURI("https://" + aLocation);
    }
    return uri;
  },

  /**
   * Detect calendars using the given information. The location can be a number
   * of things and handling this is up to the provider. It could be a hostname,
   * a specific URL, the origin URL, etc.
   *
   * @param {string} aUsername - The username for logging in.
   * @param {string} aPassword - The password for logging in.
   * @param {string} aLocation - The location information.
   * @param {boolean} aSavePassword - If true, the credentials will be saved
   *                                                      in the password manager if used.
   * @param {ProviderFilter[]} aPreDetectFilters - Functions for filtering out providers.
   * @param {object} aExtraProperties - Extra properties to pass on to the
   *                                                      providers.
   * @returns {Promise<Map<string, calICalendar[]>>} A promise resolving with a Map of
   *                                                      provider type to calendars found.
   */
  async detect(
    aUsername,
    aPassword,
    aLocation,
    aSavePassword,
    aPreDetectFilters,
    aExtraProperties
  ) {
    let providers = this.providers;

    if (!providers.size) {
      throw new calproviderdetection.NoneFoundError(
        "No providers available that implement calendar detection"
      );
    }

    // Filter out the providers that should not be used (for the location, username, etc.).
    for (let func of aPreDetectFilters) {
      let typesToFilterOut = func(providers.keys(), aLocation, aUsername);
      typesToFilterOut.forEach(type => providers.delete(type));
    }

    let resolutions = await Promise.allSettled(
      [...providers.values()].map(provider => {
        let detectionResult = provider.detectCalendars(
          aUsername,
          aPassword,
          aLocation,
          aSavePassword,
          aExtraProperties
        );
        return detectionResult.then(
          result => ({ provider, status: Cr.NS_OK, detail: result }),
          failure => ({ provider, status: Cr.NS_ERROR_FAILURE, detail: failure })
        );
      })
    );

    let failCount = 0;
    let lastError;
    let results = new Map(
      resolutions.reduce((res, resolution) => {
        let { provider, status, detail } = resolution.value || resolution.reason;

        if (Components.isSuccessCode(status) && detail && detail.length) {
          res.push([provider, detail]);
        } else {
          failCount++;
          if (detail instanceof DetectionError) {
            lastError = detail;
          }
        }

        return res;
      }, [])
    );

    // If everything failed due to one of the detection errors, then pass that on.
    if (failCount == resolutions.length) {
      throw lastError || new calproviderdetection.NoneFoundError();
    }

    return results;
  },

  /** The base detection error class */
  Error: DetectionError,

  /** An error that can be thrown if authentication failed */
  AuthFailedError: DetectionErrorClass("AuthFailedError"),

  /** An error that can be thrown if the location is invalid or has no calendars */
  NoneFoundError: DetectionErrorClass("NoneFoundError"),

  /** An error that can be thrown if the user canceled the operation */
  CanceledError: DetectionErrorClass("CanceledError"),
};