Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


SSL PushComponents.sys.mjs   Sprache: unbekannt

 
/* 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/. */

/**
 * This file exports XPCOM components for C++ and chrome JavaScript callers to
 * interact with the Push service.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

var isParent =
  Services.appinfo.processType === Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;

const lazy = {};

// The default Push service implementation.
ChromeUtils.defineLazyGetter(lazy, "PushService", function () {
  if (Services.prefs.getBoolPref("dom.push.enabled")) {
    const { PushService } = ChromeUtils.importESModule(
      "resource://gre/modules/PushService.sys.mjs"
    );
    PushService.init();
    return PushService;
  }

  throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
});

// Observer notification topics for push messages and subscription status
// changes. These are duplicated and used in `nsIPushNotifier`. They're exposed
// on `nsIPushService` so that JS callers only need to import this service.
const OBSERVER_TOPIC_PUSH = "push-message";
const OBSERVER_TOPIC_SUBSCRIPTION_CHANGE = "push-subscription-change";
const OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED = "push-subscription-modified";

/**
 * `PushServiceBase`, `PushServiceParent`, and `PushServiceContent` collectively
 * implement the `nsIPushService` interface. This interface provides calls
 * similar to the Push DOM API, but does not require service workers.
 *
 * Push service methods may be called from the parent or content process. The
 * parent process implementation loads `PushService.sys.mjs` at app startup, and
 * calls its methods directly. The content implementation forwards calls to
 * the parent Push service via IPC.
 *
 * The implementations share a class and contract ID.
 */
function PushServiceBase() {
  this.wrappedJSObject = this;
  this._addListeners();
}

PushServiceBase.prototype = {
  classID: Components.ID("{daaa8d73-677e-4233-8acd-2c404bd01658}"),
  contractID: "@mozilla.org/push/Service;1",
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
    "nsIPushService",
    "nsIPushQuotaManager",
    "nsIPushErrorReporter",
  ]),

  pushTopic: OBSERVER_TOPIC_PUSH,
  subscriptionChangeTopic: OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
  subscriptionModifiedTopic: OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED,

  ensureReady() {},

  _addListeners() {
    for (let message of this._messages) {
      this._mm.addMessageListener(message, this);
    }
  },

  _isValidMessage(message) {
    return this._messages.includes(message.name);
  },

  observe(subject, topic) {
    if (topic === "android-push-service") {
      // Load PushService immediately.
      this.ensureReady();
    }
  },

  _deliverSubscription(request, props) {
    if (!props) {
      request.onPushSubscription(Cr.NS_OK, null);
      return;
    }
    request.onPushSubscription(Cr.NS_OK, new ChromePushSubscription(props));
  },

  _deliverSubscriptionError(request, error) {
    let result =
      typeof error.result == "number" ? error.result : Cr.NS_ERROR_FAILURE;
    request.onPushSubscription(result, null);
  },
};

/**
 * The parent process implementation of `nsIPushService`. This version loads
 * `PushService.sys.mjs` at startup and calls its methods directly. It also
 * receives and responds to requests from the content process.
 */
let parentInstance;
function PushServiceParent() {
  if (parentInstance) {
    return parentInstance;
  }
  parentInstance = this;

  PushServiceBase.call(this);
}

PushServiceParent.prototype = Object.create(PushServiceBase.prototype);

XPCOMUtils.defineLazyServiceGetter(
  PushServiceParent.prototype,
  "_mm",
  "@mozilla.org/parentprocessmessagemanager;1",
  "nsISupports"
);

Object.assign(PushServiceParent.prototype, {
  _messages: [
    "Push:Register",
    "Push:Registration",
    "Push:Unregister",
    "Push:Clear",
    "Push:ReportError",
  ],

  // nsIPushService methods

  subscribe(scope, principal, callback) {
    this.subscribeWithKey(scope, principal, [], callback);
  },

  subscribeWithKey(scope, principal, key, callback) {
    this._handleRequest("Push:Register", principal, {
      scope,
      appServerKey: key,
    })
      .then(
        result => {
          this._deliverSubscription(callback, result);
        },
        error => {
          this._deliverSubscriptionError(callback, error);
        }
      )
      .catch(console.error);
  },

  unsubscribe(scope, principal, callback) {
    this._handleRequest("Push:Unregister", principal, {
      scope,
    })
      .then(
        result => {
          callback.onUnsubscribe(Cr.NS_OK, result);
        },
        () => {
          callback.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
        }
      )
      .catch(console.error);
  },

  getSubscription(scope, principal, callback) {
    return this._handleRequest("Push:Registration", principal, {
      scope,
    })
      .then(
        result => {
          this._deliverSubscription(callback, result);
        },
        error => {
          this._deliverSubscriptionError(callback, error);
        }
      )
      .catch(console.error);
  },

  clearForDomain(domain, originAttributesPattern, callback) {
    return this._handleRequest("Push:Clear", null, {
      domain,
      originAttributesPattern,
    })
      .then(
        () => {
          callback.onClear(Cr.NS_OK);
        },
        () => {
          callback.onClear(Cr.NS_ERROR_FAILURE);
        }
      )
      .catch(console.error);
  },

  clearForPrincipal(principal, callback) {
    return this._handleRequest("Push:Clear", null, {
      principal,
    })
      .then(
        () => {
          callback.onClear(Cr.NS_OK);
        },
        () => {
          callback.onClear(Cr.NS_ERROR_FAILURE);
        }
      )
      .catch(console.error);
  },

  // nsIPushQuotaManager methods

  notificationForOriginShown(origin) {
    this.service.notificationForOriginShown(origin);
  },

  notificationForOriginClosed(origin) {
    this.service.notificationForOriginClosed(origin);
  },

  // nsIPushErrorReporter methods

  reportDeliveryError(messageId, reason) {
    this.service.reportDeliveryError(messageId, reason);
  },

  receiveMessage(message) {
    if (!this._isValidMessage(message)) {
      return;
    }
    let { name, target, data } = message;
    if (name === "Push:ReportError") {
      this.reportDeliveryError(data.messageId, data.reason);
      return;
    }
    this._handleRequest(name, data.principal, data)
      .then(
        result => {
          target.sendAsyncMessage(this._getResponseName(name, "OK"), {
            requestID: data.requestID,
            result,
          });
        },
        error => {
          target.sendAsyncMessage(this._getResponseName(name, "KO"), {
            requestID: data.requestID,
            result: error.result,
          });
        }
      )
      .catch(console.error);
  },

  ensureReady() {
    this.service.init();
  },

  _toPageRecord(principal, data) {
    if (!data.scope) {
      throw new Error("Invalid page record: missing scope");
    }
    if (!principal) {
      throw new Error("Invalid page record: missing principal");
    }
    if (principal.isNullPrincipal || principal.isExpandedPrincipal) {
      throw new Error("Invalid page record: unsupported principal");
    }

    // System subscriptions can only be created by chrome callers, and are
    // exempt from the background message quota and permission checks. They
    // also do not fire service worker events.
    data.systemRecord = principal.isSystemPrincipal;

    data.originAttributes = ChromeUtils.originAttributesToSuffix(
      principal.originAttributes
    );

    return data;
  },

  async _handleRequest(name, principal, data) {
    if (name == "Push:Clear") {
      return this.service.clear(data);
    }

    let pageRecord;
    try {
      pageRecord = this._toPageRecord(principal, data);
    } catch (e) {
      return Promise.reject(e);
    }

    if (name === "Push:Register") {
      return this.service.register(pageRecord);
    }
    if (name === "Push:Registration") {
      return this.service.registration(pageRecord);
    }
    if (name === "Push:Unregister") {
      return this.service.unregister(pageRecord);
    }

    return Promise.reject(new Error("Invalid request: unknown name"));
  },

  _getResponseName(requestName, suffix) {
    let name = requestName.slice("Push:".length);
    return "PushService:" + name + ":" + suffix;
  },

  // Methods used for mocking in tests.

  replaceServiceBackend(options) {
    return this.service.changeTestServer(options.serverURI, options);
  },

  restoreServiceBackend() {
    var defaultServerURL = Services.prefs.getCharPref("dom.push.serverURL");
    return this.service.changeTestServer(defaultServerURL);
  },
});

// Used to replace the implementation with a mock.
Object.defineProperty(PushServiceParent.prototype, "service", {
  get() {
    return this._service || lazy.PushService;
  },
  set(impl) {
    this._service = impl;
  },
});

let contentInstance;
/**
 * The content process implementation of `nsIPushService`. This version
 * uses the child message manager to forward calls to the parent process.
 * The parent Push service instance handles the request, and responds with a
 * message containing the result.
 */
function PushServiceContent() {
  if (contentInstance) {
    return contentInstance;
  }
  contentInstance = this;

  PushServiceBase.apply(this, arguments);
  this._requests = new Map();
  this._requestId = 0;
}

PushServiceContent.prototype = Object.create(PushServiceBase.prototype);

XPCOMUtils.defineLazyServiceGetter(
  PushServiceContent.prototype,
  "_mm",
  "@mozilla.org/childprocessmessagemanager;1",
  "nsISupports"
);

Object.assign(PushServiceContent.prototype, {
  _messages: [
    "PushService:Register:OK",
    "PushService:Register:KO",
    "PushService:Registration:OK",
    "PushService:Registration:KO",
    "PushService:Unregister:OK",
    "PushService:Unregister:KO",
    "PushService:Clear:OK",
    "PushService:Clear:KO",
  ],

  // nsIPushService methods

  subscribe(scope, principal, callback) {
    this.subscribeWithKey(scope, principal, [], callback);
  },

  subscribeWithKey(scope, principal, key, callback) {
    let requestID = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Register", {
      scope,
      appServerKey: key,
      requestID,
      principal,
    });
  },

  unsubscribe(scope, principal, callback) {
    let requestID = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Unregister", {
      scope,
      requestID,
      principal,
    });
  },

  getSubscription(scope, principal, callback) {
    let requestID = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Registration", {
      scope,
      requestID,
      principal,
    });
  },

  clearForDomain(domain, callback) {
    let requestID = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Clear", {
      domain,
      requestID,
    });
  },

  // nsIPushErrorReporter methods

  reportDeliveryError(messageId, reason) {
    this._mm.sendAsyncMessage("Push:ReportError", {
      messageId,
      reason,
    });
  },

  _addRequest(data) {
    let id = ++this._requestId;
    this._requests.set(id, data);
    return id;
  },

  _takeRequest(requestId) {
    let d = this._requests.get(requestId);
    this._requests.delete(requestId);
    return d;
  },

  receiveMessage(message) {
    if (!this._isValidMessage(message)) {
      return;
    }
    let { name, data } = message;
    let request = this._takeRequest(data.requestID);

    if (!request) {
      return;
    }

    switch (name) {
      case "PushService:Register:OK":
      case "PushService:Registration:OK":
        this._deliverSubscription(request, data.result);
        break;

      case "PushService:Register:KO":
      case "PushService:Registration:KO":
        this._deliverSubscriptionError(request, data);
        break;

      case "PushService:Unregister:OK":
        if (typeof data.result === "boolean") {
          request.onUnsubscribe(Cr.NS_OK, data.result);
        } else {
          request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
        }
        break;

      case "PushService:Unregister:KO":
        request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
        break;

      case "PushService:Clear:OK":
        request.onClear(Cr.NS_OK);
        break;

      case "PushService:Clear:KO":
        request.onClear(Cr.NS_ERROR_FAILURE);
        break;

      default:
        break;
    }
  },
});

/** `ChromePushSubscription` instances are passed to all subscription callbacks. */
class ChromePushSubscription {
  #props;

  constructor(props) {
    this.#props = props;
  }

  QueryInterface = ChromeUtils.generateQI(["nsIPushSubscription"]);

  /** The URL for sending messages to this subscription. */
  get endpoint() {
    return this.#props.endpoint;
  }

  /** The last time a message was sent to this subscription. */
  get lastPush() {
    return this.#props.lastPush;
  }

  /** The total number of messages sent to this subscription. */
  get pushCount() {
    return this.#props.pushCount;
  }

  /** The number of remaining background messages that can be sent to this
   * subscription, or -1 of the subscription is exempt from the quota.
   */
  get quota() {
    return this.#props.quota;
  }

  /**
   * Indicates whether this subscription was created with the system principal.
   * System subscriptions are exempt from the background message quota and
   * permission checks.
   */
  get isSystemSubscription() {
    return !!this.#props.systemRecord;
  }

  /** The private key used to decrypt incoming push messages, in JWK format */
  get p256dhPrivateKey() {
    return this.#props.p256dhPrivateKey;
  }

  /**
   * Indicates whether this subscription is subject to the background message
   * quota.
   */
  quotaApplies() {
    return this.quota >= 0;
  }

  /**
   * Indicates whether this subscription exceeded the background message quota,
   * or the user revoked the notification permission. The caller must request a
   * new subscription to continue receiving push messages.
   */
  isExpired() {
    return this.quota === 0;
  }

  /**
   * Returns a key for encrypting messages sent to this subscription. JS
   * callers receive the key buffer as a return value, while C++ callers
   * receive the key size and buffer as out parameters.
   */
  getKey(name) {
    switch (name) {
      case "p256dh":
        return this.#getRawKey(this.#props.p256dhKey);

      case "auth":
        return this.#getRawKey(this.#props.authenticationSecret);

      case "appServer":
        return this.#getRawKey(this.#props.appServerKey);
    }
    return [];
  }

  #getRawKey(key) {
    if (!key) {
      return [];
    }
    return new Uint8Array(key);
  }
}

// Export the correct implementation depending on whether we're running in
// the parent or content process.
export let Service = isParent ? PushServiceParent : PushServiceContent;

[ Verzeichnis aufwärts0.39unsichere Verbindung  Übersetzung europäischer Sprachen durch Browser  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge