tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

GeckoViewPush.sys.mjs (7043B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
      6 
      7 const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewPush");
      8 
      9 const lazy = {};
     10 
     11 ChromeUtils.defineESModuleGetters(lazy, {
     12  EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
     13  PushCrypto: "resource://gre/modules/PushCrypto.sys.mjs",
     14 });
     15 
     16 // Observer notification topics for push messages and subscription status
     17 // changes. These are duplicated and used in `nsIPushNotifier`. They're exposed
     18 // on `nsIPushService` so that JS callers only need to import this service.
     19 const OBSERVER_TOPIC_PUSH = "push-message";
     20 const OBSERVER_TOPIC_SUBSCRIPTION_CHANGE = "push-subscription-change";
     21 const OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED = "push-subscription-modified";
     22 
     23 function createSubscription({
     24  scope,
     25  browserPublicKey,
     26  authSecret,
     27  endpoint,
     28  appServerKey,
     29 }) {
     30  const decodedBrowserKey = ChromeUtils.base64URLDecode(browserPublicKey, {
     31    padding: "ignore",
     32  });
     33  const decodedAuthSecret = ChromeUtils.base64URLDecode(authSecret, {
     34    padding: "ignore",
     35  });
     36 
     37  return new PushSubscription({
     38    endpoint,
     39    scope,
     40    p256dhKey: decodedBrowserKey,
     41    authenticationSecret: decodedAuthSecret,
     42    appServerKey,
     43  });
     44 }
     45 
     46 function scopeWithAttrs(scope, attrs) {
     47  return scope + ChromeUtils.originAttributesToSuffix(attrs);
     48 }
     49 
     50 export class PushService {
     51  constructor() {
     52    this.wrappedJSObject = this;
     53  }
     54 
     55  pushTopic = OBSERVER_TOPIC_PUSH;
     56  subscriptionChangeTopic = OBSERVER_TOPIC_SUBSCRIPTION_CHANGE;
     57  subscriptionModifiedTopic = OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED;
     58 
     59  // nsIObserver methods
     60 
     61  observe() {}
     62 
     63  // nsIPushService methods
     64 
     65  subscribe(scope, principal, callback) {
     66    this.subscribeWithKey(scope, principal, null, callback);
     67  }
     68 
     69  async subscribeWithKey(scope, principal, appServerKey, callback) {
     70    const keyView = new Uint8Array(appServerKey);
     71 
     72    if (appServerKey != null) {
     73      try {
     74        await lazy.PushCrypto.validateAppServerKey(keyView);
     75      } catch (error) {
     76        callback.onPushSubscription(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR, null);
     77        return;
     78      }
     79    }
     80 
     81    try {
     82      const response = await lazy.EventDispatcher.instance.sendRequestForResult(
     83        {
     84          type: "GeckoView:PushSubscribe",
     85          scope: scopeWithAttrs(scope, principal.originAttributes),
     86          appServerKey: appServerKey
     87            ? ChromeUtils.base64URLEncode(keyView, {
     88                pad: true,
     89              })
     90            : null,
     91        }
     92      );
     93 
     94      let subscription = null;
     95      if (response) {
     96        subscription = createSubscription({
     97          ...response,
     98          scope,
     99          principal,
    100          appServerKey,
    101        });
    102      }
    103 
    104      callback.onPushSubscription(Cr.NS_OK, subscription);
    105    } catch (e) {
    106      callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null);
    107    }
    108  }
    109 
    110  async unsubscribe(scope, principal, callback) {
    111    try {
    112      await lazy.EventDispatcher.instance.sendRequestForResult({
    113        type: "GeckoView:PushUnsubscribe",
    114        scope: scopeWithAttrs(scope, principal.originAttributes),
    115      });
    116 
    117      callback.onUnsubscribe(Cr.NS_OK, true);
    118    } catch (e) {
    119      callback.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
    120    }
    121  }
    122 
    123  async getSubscription(scope, principal, callback) {
    124    try {
    125      const response = await lazy.EventDispatcher.instance.sendRequestForResult(
    126        {
    127          type: "GeckoView:PushGetSubscription",
    128          scope: scopeWithAttrs(scope, principal.originAttributes),
    129        }
    130      );
    131 
    132      let subscription = null;
    133      if (response) {
    134        subscription = createSubscription({
    135          ...response,
    136          scope,
    137          principal,
    138        });
    139      }
    140 
    141      callback.onPushSubscription(Cr.NS_OK, subscription);
    142    } catch (e) {
    143      callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null);
    144    }
    145  }
    146 
    147  clearForDomain(domain, originAttributesPattern, callback) {
    148    callback.onClear(Cr.NS_OK);
    149  }
    150 
    151  clearForPrincipal(principal, callback) {
    152    callback.onClear(Cr.NS_OK);
    153  }
    154 
    155  // nsIPushQuotaManager methods
    156 
    157  notificationForOriginShown() {}
    158 
    159  notificationForOriginClosed() {}
    160 
    161  // nsIPushErrorReporter methods
    162 
    163  reportDeliveryError() {}
    164 }
    165 
    166 PushService.prototype.classID = Components.ID(
    167  "{a54d84d7-98a4-4fec-b664-e42e512ae9cc}"
    168 );
    169 PushService.prototype.contractID = "@mozilla.org/push/Service;1";
    170 PushService.prototype.QueryInterface = ChromeUtils.generateQI([
    171  "nsIObserver",
    172  "nsISupportsWeakReference",
    173  "nsIPushService",
    174  "nsIPushQuotaManager",
    175  "nsIPushErrorReporter",
    176 ]);
    177 
    178 /** `PushSubscription` instances are passed to all subscription callbacks. */
    179 class PushSubscription {
    180  constructor(props) {
    181    this._props = props;
    182  }
    183 
    184  /** The URL for sending messages to this subscription. */
    185  get endpoint() {
    186    return this._props.endpoint;
    187  }
    188 
    189  /** The last time a message was sent to this subscription. */
    190  get lastPush() {
    191    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
    192  }
    193 
    194  /** The total number of messages sent to this subscription. */
    195  get pushCount() {
    196    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
    197  }
    198 
    199  /**
    200   * The app will take care of throttling, so we don't
    201   * care about the quota stuff here.
    202   */
    203  get quota() {
    204    return -1;
    205  }
    206 
    207  /**
    208   * Indicates whether this subscription was created with the system principal.
    209   * System subscriptions are exempt from the background message quota and
    210   * permission checks.
    211   */
    212  get isSystemSubscription() {
    213    return false;
    214  }
    215 
    216  /** The private key used to decrypt incoming push messages, in JWK format */
    217  get p256dhPrivateKey() {
    218    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
    219  }
    220 
    221  /**
    222   * Indicates whether this subscription is subject to the background message
    223   * quota.
    224   */
    225  quotaApplies() {
    226    return false;
    227  }
    228 
    229  /**
    230   * Indicates whether this subscription exceeded the background message quota,
    231   * or the user revoked the notification permission. The caller must request a
    232   * new subscription to continue receiving push messages.
    233   */
    234  isExpired() {
    235    return false;
    236  }
    237 
    238  /**
    239   * Returns a key for encrypting messages sent to this subscription. JS
    240   * callers receive the key buffer as a return value, while C++ callers
    241   * receive the key size and buffer as out parameters.
    242   */
    243  getKey(name) {
    244    switch (name) {
    245      case "p256dh":
    246        return this._getRawKey(this._props.p256dhKey);
    247 
    248      case "auth":
    249        return this._getRawKey(this._props.authenticationSecret);
    250 
    251      case "appServer":
    252        return this._getRawKey(this._props.appServerKey);
    253    }
    254    return [];
    255  }
    256 
    257  _getRawKey(key) {
    258    if (!key) {
    259      return [];
    260    }
    261    return new Uint8Array(key);
    262  }
    263 }
    264 
    265 PushSubscription.prototype.QueryInterface = ChromeUtils.generateQI([
    266  "nsIPushSubscription",
    267 ]);