text:
package org.axonframework.eventstore.redis;

import org.axonframework.common.io.IOUtils;
import org.axonframework.domain.DomainEventMessage;
import org.axonframework.domain.DomainEventStream;
import org.axonframework.eventstore.EventStore;
import org.axonframework.repository.ConcurrencyException;
import org.axonframework.serializer.Serializer;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

/**
 * @author Allard Buijze
 */
public class RedisEventStore implements EventStore {

    private Serializer eventSerializer;
    private RedisConnectionProvider redisConnectionProvider;

    @Override
    public void appendEvents(String type, final DomainEventStream events) {
        Jedis jedis = redisConnectionProvider.newConnection();
        DomainEventMessage firstEvent = events.peek();
        final byte[] key = (type + "." + firstEvent.getAggregateIdentifier()).getBytes(IOUtils.UTF8);
        jedis.watch(key);
        Long eventCount = jedis.llen(key);
        if ((firstEvent.getSequenceNumber() != 0 && eventCount == null)
                || firstEvent.getSequenceNumber() != eventCount) {
            jedis.unwatch();
            throw new ConcurrencyException(
                    String.format("Concurrent modification detected for Aggregate identifier [%s], sequence: [%s]",
                                  firstEvent.getAggregateIdentifier(),
                                  firstEvent.getSequenceNumber()));
        }

        try {
//            jedis.pipelined(new PipelineBlock() {
//                @Override
//                public void execute() {
            Transaction multi = jedis.multi();
            while (events.hasNext()) {
                DomainEventMessage domainEvent = events.next();
                multi.rpush(new String(key, IOUtils.UTF8), new String(eventSerializer.serialize(domainEvent, byte[].class)
                                                                             .getData(), IOUtils.UTF8));
            }
            multi.exec();
//                }
//            });
//            redis.clients.jedis.Transaction transaction = jedis.multi();
//            while (events.hasNext()) {
//                DomainEvent domainEvent = events.next();
//
//                transaction.rpush(new String(key, UTF8), new String(eventSerializer.serialize(domainEvent), UTF8));
//            }
//            transaction.exec();
        } finally {
            redisConnectionProvider.closeConnection(jedis);
        }
    }

    @Override
    public DomainEventStream readEvents(String type, Object identifier) {
        throw new UnsupportedOperationException("Method not yet implemented");
    }

    public void setEventSerializer(Serializer serializer) {
        this.eventSerializer = serializer;
    }

    public void setRedisConnectionProvider(RedisConnectionProvider redisConnectionProvider) {
        this.redisConnectionProvider = redisConnectionProvider;
    }
}


text:
namespace media {

// Parser for WebM Info element.
class WebMInfoParser : public WebMParserClient {
 public:
  WebMInfoParser();
  virtual ~WebMInfoParser();

  // Parses a WebM Info element in |buf|.
  //
  // Returns -1 if the parse fails.
  // Returns 0 if more data is needed.
  // Returns the number of bytes parsed on success.
  int Parse(const uint8* buf, int size);

  int64 timecode_scale() const { return timecode_scale_; }
  double duration() const { return duration_; }

 private:
  // WebMParserClient methods
  virtual WebMParserClient* OnListStart(int id) OVERRIDE;
  virtual bool OnListEnd(int id) OVERRIDE;
  virtual bool OnUInt(int id, int64 val) OVERRIDE;
  virtual bool OnFloat(int id, double val) OVERRIDE;
  virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE;
  virtual bool OnString(int id, const std::string& str) OVERRIDE;

  int64 timecode_scale_;
  double duration_;

  DISALLOW_COPY_AND_ASSIGN(WebMInfoParser);
};

}  // namespace media

#endif  // MEDIA_WEBM_WEBM_INFO_PARSER_H_

redpajama_set_name: RedPajamaGithub


text:
package org.tanrabad.survey.repository.persistence;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import org.tanrabad.survey.TanrabadApp;
import org.tanrabad.survey.domain.address.DistrictRepository;
import org.tanrabad.survey.entity.lookup.District;
import org.tanrabad.survey.utils.collection.CursorList;
import org.tanrabad.survey.utils.collection.CursorMapper;

import java.util.List;

public class DbDistrictRepository extends DbRepository implements DistrictRepository {

    public static final String TABLE_NAME = "district";
    private static DbDistrictRepository instance;

    private DbDistrictRepository(Context context) {
        super(context);
    }

    public static DbDistrictRepository getInstance() {
        if (instance == null) {
            instance = new DbDistrictRepository(TanrabadApp.getInstance());
        }
        return instance;
    }

    @Override
    public List<District> find() {
        SQLiteDatabase db = readableDatabase();
        Cursor districtCursor = db.query(TABLE_NAME, DistrictColumn.WILDCARD,
            null, null, null, null, null);
        List<District> districts = new CursorList<>(districtCursor, getMapper(districtCursor));
        db.close();
        return districts;
    }

    @Override
    public List<District> findByProvinceCode(String provinceCode) {
        SQLiteDatabase db = readableDatabase();
        Cursor districtCursor = db.query(TABLE_NAME, DistrictColumn.WILDCARD, DistrictColumn.PROVINCE_CODE + "=?",
                new String[]{provinceCode}, null, null, null);
        List<District> districts = new CursorList<>(districtCursor, getMapper(districtCursor));
        db.close();
        return districts;
    }

    private DistrictCursorMapper getMapper(Cursor districtCursor) {
        return new DistrictCursorMapper(districtCursor);
    }

    @Override
    public District findByCode(String districtCode) {
        SQLiteDatabase db = readableDatabase();
        Cursor cursor = db.query(TABLE_NAME, DistrictColumn.WILDCARD, DistrictColumn.CODE + "=?",
                new String[]{districtCode}, null, null, null);
        District district = null;
        if (cursor.moveToFirst()) {
            district = getMapper(cursor).map(cursor);
        }
        cursor.close();
        db.close();
        return district;
    }

    @Override
    public boolean save(District district) {
        return false;
    }

    @Override
    public boolean update(District district) {
        return false;
    }

    @Override
    public boolean delete(District data) {
        return false;
    }

    @Override
    public void updateOrInsert(List<District> districts) {
        SQLiteDatabase db = writableDatabase();
        db.beginTransaction();
        for (District district : districts) {
            ContentValues cv = provinceContentValues(district);
            boolean update = updateWithContentValues(db, cv);
            if (!update)
                insertWithContentValue(db, cv);
        }
        db.setTransactionSuccessful();
        db.endTransaction();
        db.close();
    }

    private boolean insertWithContentValue(SQLiteDatabase db, ContentValues cv) {
        return db.insert("district", null, cv) != -1;
    }

    private boolean updateWithContentValues(SQLiteDatabase db, ContentValues cv) {
        return db.update("district", cv, "district_code=?", new String[]{cv.getAsString("district_code")}) > 0;
    }

    private ContentValues provinceContentValues(District district) {
        ContentValues cv = new ContentValues();
        cv.put("district_code", district.getCode());
        cv.put("name", district.getName());
        cv.put("province_code", district.getProvinceCode());
        return cv;
    }

    public static class DistrictColumn {
        public static final String CODE = "district_code";
        public static final String PROVINCE_CODE = "province_code";
        public static final String NAME = "name";
        public static final String BOUNDARY = "boundary";
        public static final String[] WILDCARD = new String[]{CODE, NAME, PROVINCE_CODE, BOUNDARY};
    }

    public static class DistrictCursorMapper implements CursorMapper<District> {

        int codeIdx;
        int nameIdx;
        int provinceCodeIdx;
        int boundaryIdx;

        public DistrictCursorMapper(Cursor cursor) {
            codeIdx = cursor.getColumnIndex(DistrictColumn.CODE);
            nameIdx = cursor.getColumnIndex(DistrictColumn.NAME);
            provinceCodeIdx = cursor.getColumnIndex(DistrictColumn.PROVINCE_CODE);
            boundaryIdx = cursor.getColumnIndex(DistrictColumn.BOUNDARY);
        }

        @Override
        public District map(Cursor cursor) {
            District district = new District();
            district.setCode(cursor.getString(codeIdx));
            district.setName(cursor.getString(nameIdx));
            district.setProvinceCode(cursor.getString(provinceCodeIdx));
            //TODO parse boundary text in db to BoundaryObject
            return district;
        }
    }
}


text:
import requests
# import xml.etree.ElementTree as ET
# import lxml
from bs4 import BeautifulSoup, CData, Tag
# import markdown
# import bleach

def add_spacing(tabs=0, space='  '):
    gap = ''
    num = 0
    while num < tabs:
        gap = gap + space
        num = num + 1
    return gap

def fix_image(html):
    """"""
    locate = html.find('= 0:
        left = html[:locate]
        right = html[locate:]
        right = right.replace('>', ' />', 1)
        html = left + fix_image(right)
    return html

def create_tag(parent, tag_name, class_name=None, id_number=None):
    """
    Create a new tag with tag_name and its attribute attrs_name and append it as
    the child of the parent tag.

    params:
    parent       -- the parent of the created tag
    tag_name     -- name of the tag. For example,
 has name "div"
    attrs_name   -- optional. Name of the tag's attribute. For example,
 has attribute_name "answer">.

    returns:
    The created tag.
    """
    if (id_number):
        child = Tag(name=tag_name, attrs={'class': class_name, 'id': id_number})
    if class_name:
        child = Tag(name=tag_name, attrs={'class': class_name})
    else:
        child = Tag(name=tag_name)
    try:
        parent.append(child)
    except AttributeError:
        print ("Object " + str(type(parent)) + " does not have attribute append.")
    except Exception as detail:
            print ("Get exception when appending tag:", detail)
    return child

class MarkdownHelper(object):
    def add_clean_markdown(self, soup, tag_type, tag_class='', attribs=None):
        if(tag_class):
            return
        if(attribs):
            marker = '$' if tag_type == 'span' else '$$'
            for tag in soup(tag_type, attrs=attribs):
                try:
                    if 'CDATA' in tag['data-math']:
                        temp_tag = str(tag)
                        math = ''
                        tag_parts = temp_tag.split('"')
                        count = 0
                        if tag_parts is []:
                            break
                        for section in tag_parts:
                            count += 1
                            if 'CDATA' in section:
                                section = section.replace('% <![CDATA[\n',
                                                          '"' + marker)
                                section = section.replace(' %]]>',
                                                          marker + '"')
                                section = section.replace('% <![CDATA[\n',
                                                          marker)
                                section = section.replace(' %]]>', marker)
                                if section[:2] == '"$':
                                    math = section[1:-1]
                            if str(section) == '>% ':
                                section = '>' + math + ''
                            tag_parts[count - 1] = section
                        temp_tag = ''.join(tag_parts)
                    tag.string.replace_with(marker + tag.string + marker)
                    tag['data-math'] = '\n' if marker is '$$' else '' + \
                                       marker + tag['data-math'] + marker + \
                                       '\n' if marker is '$$' else ''
                except AttributeError:
                    continue
                except Exception as e:
                    raise e
            return

    def add_latex(self, html):
        """"""
        import re
        html = re.sub("________", "", html)
        html = re.sub(r"\\rm", r"", html)  # r"\\text{", html)
        html = re.sub(r"-", r"-", html)
        html = re.sub(r"\\gt", r'>', html)
        html = re.sub(r"\\lt", r'<', html)
        html = re.sub(r"2!", r'2\!', html)
        html = re.sub(r",!", r",\!", html)
        html = re.sub(r"c!", r"c\!", html)
        html = re.sub(r"!\cdot!", r"\!-\cdot-\!", html)
        html = re.sub("\!$$", "$$", html)
        html = re.sub(r"2\\\\!\\!", "2", html)
        html = re.sub(r"\|", r"|", html)
        html = re.sub(r"(% ((<)|<)!\[CDATA\[\n)", r"", html)
        html = re.sub(r"( %]](>|>))", r"", html)
        with open('index2.txt', 'w+') as output:
            output.write(html)
        soup = BeautifulSoup(html, 'html.parser')
        for nav_string in soup(text=True):
            if isinstance(nav_string, CData):
                tag = Tag(soup, name="math")
                tag.insert(0, nav_string[:])
                nav_string.replace_with(tag)
        self.add_clean_markdown(soup, 'span', attribs={"data-math": True})
        self.add_clean_markdown(soup, 'div', attribs={"data-math": True})
        self.add_clean_markdown(soup, 'div', tag_class='stem_text')
        self.add_clean_markdown(soup, 'div', tag_class='answer_text')
        self.add_clean_markdown(soup, 'div', tag_class='feedback')
        return soup

class Portfolio(object):
    def __init__(self):
        self.problems = []
        self.total = 0

    def get_exercises(self, filename=None, url=None, number_of_questions=1):
        """"""
        if url:
            query = requests.get(url)
            try:
                data_block = query.json()
            except ValueError:
                return {}
            return data_block
        with open(filename, 'r') as f:
            return f.read()

    def import_exercises(self, location, is_url=True):
        """"""
        if is_url:
            data = self.get_exercises(url=location)
        else:
            data = self.get_exercises(filename=location)
        if 'total_count' in data:
            self.total = data['total_count']
            for exercise in data['items']:
                self.problems.append(self.to_exercise(exercise))
        else:
            self.problems.append(self.to_exercise(data))
            self.total = self.total + 1

    def to_string(self, position=0):
        """"""
        output = ""
        for key, value in self.problems[position].items():
            output = '%s%s: %s\n' % (output, key, value)
        return output

    def to_exercise(self, ex_json):
        return Exercise(ex_json)

    def to_html(self, indent=0):
        """"""
        start = None
        html = BeautifulSoup('\n', 'html.parser')
        end = None
        with open('side1.html', 'r') as left:
            start = left.read()
        with open('side2.html', 'r') as right:
            end = right.read()
        for ex in self.problems:
            ex.build_soup(html, 5)
        with open('index2.html', 'w+') as output:
            output.write(start)
            output.write(html.prettify())
            output.write(end)

    def to_csv(self, filename='ex_list.csv', tag_types=['-ex']):
        csv = 'NumAtVersion,Tag\n'
        for tag in tag_types:
            exercise_list = self.get_id_to_tags(tag)
            for exercise in exercise_list:
                csv = csv + '%s,%s\n' % (exercise, exercise_list[exercise])
        with open(filename, 'w+') as output:
            output.write(csv)

    def get_id_to_tags(self, tag_type):
        """"""
        exercise_list = {}
        for exercise in self.problems:
            for tag in exercise.tags:
                if tag_type in tag:
                    exercise_list[exercise.uid] = tag
        return exercise_list

class Exercise(object):
    def __init__(self, ex_json=None):
        self.uid = ex_json['uid'] if 'uid' in ex_json else ''
        self.number = ex_json['number'] if 'number' in ex_json else 0
        self.version = ex_json['version'] if 'version' in ex_json else 0
        self.published_at = ex_json['published_at'] \
            if 'published_at' in ex_json else ''
        self.editors = ex_json['editors'] if 'editors' in ex_json else []
        self.authors = ex_json['authors'] if 'authors' in ex_json else []
        self.copyright_holders = ex_json['copyright_holders'] \
            if 'copyright_holders' in ex_json else []
        self.derived_from = ex_json['derived_from'] \
            if 'derived_from' in ex_json else []
        self.attachments = ex_json['attachments'] \
            if 'attachments' in ex_json else []
        self.tags = ex_json['tags'] if 'tags' in ex_json else ''
        self.stimulus_html = ex_json['stimulus_html'] \
            if 'stimulus_html' in ex_json else ''
        self.question = Question(self.uid, ex_json['questions'])
        self.type = self.get_question_type(self.tags)

    def build_soup(self, parent_tag, indent=0):
        """"""
        tags = ''
        for tag in self.tags:
            tags = tags + tag + ' '
        tags = tags[:-1]
        article = create_tag(parent_tag, "article", "exercise " + self.type, str(self.number) + "-" + str(self.version))
        header = create_tag(article, "header")
        question_id = create_tag(header, "div", "question_id")
        h1 = create_tag(question_id, "h1")
        h1.append("ID: " + self.uid)
        published = create_tag(header, "div", "published")
        p1 = create_tag(published, "p")
        p1.append(self.published_at)
        self.question.build_soup(article)
        footer = create_tag(article, "footer")
        question_info_wrapper = create_tag(footer, "div", "question_info_wrapper")
        question_info = create_tag(question_info_wrapper, "div", "question_info")
        h3 = create_tag(question_info, "h3")
        h3.append("Tags:")
        p2 = create_tag(question_info, "p")
        p2.append(tags)

        # html = \
        #     '''
        #

        #
        #

        #
ID: %s

        #

        #

        #
%s

        #

        #
        #       %s
        #
        #

        #

        #
Tags:

        #
%s

        #

        #

        #
        #

        #     ''' % (' ' + self.type,
        #            self.number,
        #            self.version,
        #            self.uid,
        #            self.published_at,
        #            self.question.to_html(),
        #            tags)

    def get_question_type(self, tags):
        if 'os-practice-problems' in tags or 'os-practice-concepts' in tags:
            return 'practice'
        if 'ost-chapter-review, problem' in tags or \
            'ost-chapter-review, concept' in tags or \
                'ost-chapter-review, critical-thinking' in tags:
            return 'review'
        if 'ost-test-prep, multiple-choice' in tags or \
            'ost-test-prep, short-answer' in tags or \
                'ost-test-prep, extended-response' in tags:
            return 'test_prep'
        return 'grasp_check'

    def write_to_index(self, html, target_id):
        """"""
        try:
            with open('exercises.html', 'r+') as index:
                print(index.readline())
        except Exception as e:
            print(e)

    def add_question(self, questions):
        """"""

class Question(object):
    def __init__(self, parent='', question_data=None):
        self.parent = parent
        self.answers = []
        if question_data is not None:
            self.stimulus_html = question_data[0]['stimulus_html']
            self.stem_html = question_data[0]['stem_html']
            self.hints = question_data[0]['hints']
            self.formats = question_data[0]['formats']
            self.combo_choices = question_data[0]['combo_choices']
            self.id = question_data[0]['id']
            for answer in question_data[0]['answers']:
                self.answers.append(
                    Answer(answer['id'] if 'id' in answer else 0,
                           answer['content_html'] if 'content_html' in answer
                           else '',
                           answer['correctness'] if 'correctness' in answer
                           else '0.0',
                           answer['feedback_html'] if 'feedback_html' in answer
                           else '',
                           self.parent))
            if len(self.answers) >= 1:
                self.answers.sort()
                for answer in self.answers:
                    answer.choice = '' + chr(ord('A') + answer.id -
                                             self.answers[0].id)
        else:
            self.stimulus_html = ""
            self.stem_html = ""     # **********
            self.answers = []     # **********
            self.hints = []
            self.formats = []     # **********
            self.combo_choices = []
            self.id = 0     # **********

    __eq__ = lambda self, other: self.id == other.id
    __ne__ = lambda self, other: self.id != other.id
    __lt__ = lambda self, other: self.id < other.id
    __le__ = lambda self, other: self.id <= other.id
    __gt__ = lambda self, other: self.id > other.id
    __ge__ = lambda self, other: self.id >= other.id

    def build_soup(self, parent_tag, indent=0):
        markdown_helper = MarkdownHelper()
        id_number, version = self.parent.split('@')
        question = create_tag(parent_tag, "section", "question")
        stem_question = create_tag(question, "div", "stem_question")
        stem_question.append("Question:")
        stem_text = create_tag(question, "div", "stem_text")
        stem_text.append(markdown_helper.add_latex(self.stem_html))
        answers = create_tag(parent_tag, "section", "answers")

        ans_num = 1
        for answer in self.answers:
            answer.build_soup(answers)
            if ans_num < len(self.answers):
                create_tag(answers, "div", "answer_split")
                ans_num = ans_num + 1
        # html = \
        #     '''
        #

        #

        #
Question:

        #
%s

        #

        #

        #

        #     ''' % add_latex(self.stem_html)
        # ans_num = 1
        # for answer in self.answers:
        #     html = html + answer.to_html()
        #     if ans_num < len(self.answers):
        #         html = html + '

'
        #         ans_num = ans_num + 1
        # html = html + '
'

class Answer(object):
    def __init__(self, ans_id=0, content_html='', correctness='',
                 feedback_html='', parent='', letter=''):
        self.id = ans_id
        self.parent = parent
        self.correctness = correctness
        self.content_html = content_html
        self.feedback_html = feedback_html
        self.choice = letter

    __eq__ = lambda self, other: self.id == other.id
    __ne__ = lambda self, other: self.id != other.id
    __lt__ = lambda self, other: self.id < other.id
    __le__ = lambda self, other: self.id <= other.id
    __gt__ = lambda self, other: self.id > other.id
    __ge__ = lambda self, other: self.id >= other.id

    def build_soup(self, parent_tag, indent=0):
        id_number, version = self.parent.split('@')
        answer_wrapper = create_tag(parent_tag, "div", "answer_wrapper")
        selection = create_tag(answer_wrapper, "div", "selection")
        answer = create_tag(answer_wrapper, "div", "answer")
        correctness = create_tag(selection, "div", "correctness")
        letter = create_tag(selection, "div", "letter")
        answer_text_wrapper = create_tag(answer, "div", "answer_text_wrapper")
        feedback_wrapper = create_tag(answer, "div", "feedback_wrapper")
        check = create_tag(correctness, "span", 'check' if float(self.correctness) > 0.0 else '')
        span = create_tag(letter, "span")
        answer_text = create_tag(answer_text_wrapper, "div", "answer_text")
        feedback = create_tag(feedback_wrapper, "div", "feedback")

        markdown_helper = MarkdownHelper()
        span.append(self.choice + ":")
        answer_text.append(markdown_helper.add_latex(self.content_html))
        feedback.append(markdown_helper.add_latex(self.feedback_html))

        # html = \
        #     '''
        #

        #

        #

        #
        #

        #

        #           %s:
        #

        #

        #

        #

        #

        #             %s
        #

        #

        #

        #

        #             %s
        #

        #

        #

        #

        #     ''' % ('check' if float(self.correctness) > 0.0 else '',
        #            self.choice,
        #            self.content_html,
        #            self.feedback_html)


text:
using System;
using Csla;

namespace Templates
{
  [Serializable]
  public class CommandObject : CommandBase<CommandObject>
  {
    #region Authorization Methods

    public static bool CanExecuteCommand()
    {
      // TODO: customize to check user role
      //return Csla.ApplicationContext.User.IsInRole("Role");
      return true;
    }

    #endregion

    #region Factory Methods

    public static bool Execute()
    {
      if (!CanExecuteCommand())
        throw new System.Security.SecurityException("Not authorized to execute command");

      CommandObject cmd = new CommandObject();
      cmd.BeforeServer();
      cmd = DataPortal.Execute<CommandObject>(cmd);
      cmd.AfterServer();
      return cmd.Result;
    }

    #endregion

    #region Client-side Code

    public static readonly PropertyInfo<bool> ResultProperty = RegisterProperty<bool>(p => p.Result);
    public bool Result
    {
      get { return ReadProperty(ResultProperty); }
      private set { LoadProperty(ResultProperty, value); }
    }

    private void BeforeServer()
    {
      // TODO: implement code to run on client
      // before server is called
    }

    private void AfterServer()
    {
      // TODO: implement code to run on client
      // after server is called
    }

    #endregion

    #region Server-side Code

    protected override void DataPortal_Execute()
    {
      // TODO: implement code to run on server
      // and set result value(s)
      _result = true;
    }

    #endregion
  }
}


text:
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/android/search_permissions/search_permissions_service.h"

#include <memory>
#include <utility>

#include "base/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chrome/browser/android/search_permissions/search_geolocation_disclosure_tab_helper.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_decision_auto_blocker.h"
#include "components/permissions/permission_result.h"
#include "components/permissions/permission_uma_util.h"
#include "components/prefs/pref_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"

namespace {

const char kDSESettingKeyDeprecated[] = "dse_setting";

const char kGoogleURL[] = "https://www.google.com/";
const char kGoogleAusURL[] = "https://www.google.com.au/";
const char kGoogleHTTPURL[] = "http://www.google.com/";
const char kExampleURL[] = "https://www.example.com/";

url::Origin ToOrigin(const char* url) {
  return url::Origin::Create(GURL(url));
}

// The test delegate is used to mock out search-engine related functionality.
class TestSearchEngineDelegate
    : public SearchPermissionsService::SearchEngineDelegate {
 public:
  TestSearchEngineDelegate()
      : dse_origin_(url::Origin::Create(GURL(kGoogleURL))) {}
  std::u16string GetDSEName() override {
    if (dse_origin_.host().find("google") != std::string::npos)
      return u"Google";

    return u"Example";
  }

  url::Origin GetDSEOrigin() override { return dse_origin_; }

  void SetDSEChangedCallback(base::RepeatingClosure callback) override {
    dse_changed_callback_ = std::move(callback);
  }

  void ChangeDSEOrigin(const std::string& dse_origin) {
    set_dse_origin(dse_origin);
    dse_changed_callback_.Run();
  }

  void set_dse_origin(const std::string& dse_origin) {
    dse_origin_ = url::Origin::Create(GURL(dse_origin));
  }

 private:
  url::Origin dse_origin_;
  base::RepeatingClosure dse_changed_callback_;
};

}  // namespace

class SearchPermissionsServiceTest : public testing::Test {
 public:
  void SetUp() override {
    profile_ = std::make_unique<TestingProfile>();

    features_.InitAndDisableFeature(
        permissions::features::kRevertDSEAutomaticPermissions);

    // Because notification channel settings aren't tied to the profile,
    // they will persist across tests. We need to make sure they're clean
    // here.
    ClearContentSettings(ContentSettingsType::NOTIFICATIONS);

    auto test_delegate = std::make_unique<TestSearchEngineDelegate>();
    test_delegate_ = test_delegate.get();
    GetService()->SetSearchEngineDelegateForTest(std::move(test_delegate));
    ReinitializeService(true /* clear_pref */);
  }

  void TearDown() override {
    test_delegate_ = nullptr;

    // Because notification channel settings aren't tied to the profile, they
    // will persist across tests. We need to make sure they're reset here.
    ClearContentSettings(ContentSettingsType::NOTIFICATIONS);

    profile_.reset();
  }

  void ClearContentSettings(ContentSettingsType type) {
    SetContentSetting(kGoogleURL, type, CONTENT_SETTING_DEFAULT);
    SetContentSetting(kGoogleAusURL, type, CONTENT_SETTING_DEFAULT);
    SetContentSetting(kGoogleHTTPURL, type, CONTENT_SETTING_DEFAULT);
    SetContentSetting(kExampleURL, type, CONTENT_SETTING_DEFAULT);
  }

  TestingProfile* profile() { return profile_.get(); }

  TestSearchEngineDelegate* test_delegate() { return test_delegate_; }

  SearchPermissionsService* GetService() {
    return SearchPermissionsService::Factory::GetForBrowserContext(profile());
  }

  void SetContentSetting(const std::string& origin_string,
                         ContentSettingsType type,
                         ContentSetting setting) {
    GURL url(origin_string);
    HostContentSettingsMap* hcsm =
        HostContentSettingsMapFactory::GetForProfile(profile());
    // Clear a setting before setting it. This is needed because in general
    // notifications settings can't be changed from ALLOW<->BLOCK on Android O+.
    // We need to change the setting from ALLOW->BLOCK in one case, where the
    // previous DSE had permission blocked but the new DSE we're changing to has
    // permission allowed. Thus this works around that restriction.
    // WARNING: This is a special case and in general notification settings
    // should never be changed between ALLOW<->BLOCK on Android. Do not copy
    // this code. Check with the notifications team if you need to do something
    // like this.
    hcsm->SetContentSettingDefaultScope(url, url, type,
                                        CONTENT_SETTING_DEFAULT);
    hcsm->SetContentSettingDefaultScope(url, url, type, setting);
  }

  ContentSetting GetContentSetting(const std::string& origin_string,
                                   ContentSettingsType type) {
    GURL url(origin_string);
    return HostContentSettingsMapFactory::GetForProfile(profile())
        ->GetContentSetting(url, url, type);
  }

  // Simulates the initialization that happens when recreating the service. If
  // |clear_pref| is true, then it simulates the first time the service is ever
  // created.
  void ReinitializeService(bool clear_pref) {
    if (clear_pref) {
      profile()->GetPrefs()->ClearPref(prefs::kDSEPermissionsSettings);
      profile()->GetPrefs()->ClearPref(prefs::kDSEWasDisabledByPolicy);
    }

    GetService()->OnDSEChanged();
  }

  // Simulate setting the old preference to test migration.
  void SetOldPreference(bool setting) {
    base::DictionaryValue dict;
    dict.SetBoolean(kDSESettingKeyDeprecated, setting);
    profile()->GetPrefs()->Set(prefs::kDSEGeolocationSettingDeprecated, dict);
  }

 private:
  std::unique_ptr<TestingProfile> profile_;
  content::BrowserTaskEnvironment task_environment_;

  base::test::ScopedFeatureList features_;

  // This is owned by the SearchPermissionsService which is owned by the
  // profile.
  raw_ptr<TestSearchEngineDelegate> test_delegate_;
};

TEST_F(SearchPermissionsServiceTest, Initialization) {
  for (ContentSettingsType type :
       {ContentSettingsType::GEOLOCATION, ContentSettingsType::NOTIFICATIONS}) {
    // DSE setting initialized to true if the content setting is ALLOW.
    test_delegate()->ChangeDSEOrigin(kGoogleURL);
    SetContentSetting(kGoogleURL, type, CONTENT_SETTING_ALLOW);
    ReinitializeService(true /* clear_pref */);
    EXPECT_EQ(CONTENT_SETTING_ALLOW, GetContentSetting(kGoogleURL, type));
    // Check that the correct value is restored when changing the DSE.
    test_delegate()->ChangeDSEOrigin(kExampleURL);
    EXPECT_EQ(CONTENT_SETTING_ALLOW, GetContentSetting(kGoogleURL, type));

    // DSE setting initialized to true if the content setting is ASK.
    test_delegate()->ChangeDSEOrigin(kGoogleURL);
    SetContentSetting(kGoogleURL, type, CONTENT_SETTING_DEFAULT);
    EXPECT_EQ(CONTENT_SETTING_ASK, GetContentSetting(kGoogleURL, type));
    ReinitializeService(true /* clear_pref */);
    EXPECT_EQ(CONTENT_SETTING_ALLOW, GetContentSetting(kGoogleURL, type));
    test_delegate()->ChangeDSEOrigin(kExampleURL);
    EXPECT_EQ(CONTENT_SETTING_ASK, GetContentSetting(kGoogleURL, type));

    // DSE setting initialized to false if the content setting is BLOCK.
    test_delegate()->ChangeDSEOrigin(kGoogleURL);
    SetContentSetting(kGoogleURL, type, CONTENT_SETTING_BLOCK);
    ReinitializeService(true /* clear_pref */);
    EXPECT_EQ(CONTENT_SETTING_BLOCK, GetContentSetting(kGoogleURL, type));
    test_delegate()->ChangeDSEOrigin(kExampleURL);
    EXPECT_EQ(CONTENT_SETTING_BLOCK, GetContentSetting(kGoogleURL, type));

    // Nothing happens if the pref is already set when the service is
    // initialized.
    test_delegate()->ChangeDSEOrigin(kGoogleURL);
    SetContentSetting(kGoogleURL, type, CONTENT_SETTING_DEFAULT);
    ReinitializeService(false /* clear_pref */);
    EXPECT_EQ(CONTENT_SETTING_ASK, GetContentSetting(kGoogleURL, type));
  }

  // Check that the geolocation disclosure is reset.
  SearchGeolocationDisclosureTabHelper::FakeShowingDisclosureForTests(
      profile());
  ReinitializeService(true /* clear_pref */);
  EXPECT_TRUE(SearchGeolocationDisclosureTabHelper::IsDisclosureResetForTests(
      profile()));
  SearchGeolocationDisclosureTabHelper::FakeShowingDisclosureForTests(
      profile());
  ReinitializeService(false /* clear_pref */);
  EXPECT_FALSE(SearchGeolocationDisclosureTabHelper::IsDisclosureResetForTests(
      profile()));
}

TEST_F(SearchPermissionsServiceTest, InitializationInconsistent) {
  // Test initialization when the stored pref has become inconsistent with the
  // current DSE.
  test_delegate()->ChangeDSEOrigin(kGoogleURL);

  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS));

  test_delegate()->set_dse_origin(kGoogleAusURL);
  ReinitializeService(false /* clear_pref */);

  // The settings for the previous DSE should be restored when the service is
  // started.
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS));

  // The settings should be transferred to the new DSE.
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_ALLOW,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));
}

TEST_F(SearchPermissionsServiceTest, Incognito) {
  // Service isn't constructed for Incognito profile.
  Profile* incognito_profile =
      profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
  SearchPermissionsService* service =
      SearchPermissionsService::Factory::GetForBrowserContext(
          incognito_profile);
  EXPECT_EQ(nullptr, service);
}

TEST_F(SearchPermissionsServiceTest, NonPrimaryOffTheRecord) {
  // Service isn't constructed for non-primary OTR profiles.
  Profile* otr_profile = profile()->GetOffTheRecordProfile(
      Profile::OTRProfileID::CreateUniqueForTesting(),
      /*create_if_needed=*/true);
  SearchPermissionsService* service =
      SearchPermissionsService::Factory::GetForBrowserContext(otr_profile);
  EXPECT_EQ(nullptr, service);
}

TEST_F(SearchPermissionsServiceTest, Migration) {
  // When location was previously allowed for the DSE, it should be carried
  // over.
  test_delegate()->ChangeDSEOrigin(kGoogleURL);
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS));
  SetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION,
                    CONTENT_SETTING_DEFAULT);
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));
  SetOldPreference(true /* setting */);
  ReinitializeService(true /* clear_pref */);
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));

  // If location was previously blocked for the DSE, it should be carried over.
  SetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION,
                    CONTENT_SETTING_DEFAULT);
  SetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS,
                    CONTENT_SETTING_DEFAULT);
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));
  SetOldPreference(false /* setting */);
  ReinitializeService(true /* clear_pref */);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));
  // Notifications should be unaffected.
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS));
  // Changing DSE should cause the setting to go back to ASK for Google.
  test_delegate()->ChangeDSEOrigin(kExampleURL);
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));

  // Check that migrating the pref causes it to be deleted.
  SetOldPreference(false /* setting */);
  ReinitializeService(true /* clear_pref */);
  EXPECT_FALSE(profile()->GetPrefs()->HasPrefPath(
      prefs::kDSEGeolocationSettingDeprecated));

  // Check that the disclosure won't be reset if we migrate a pref.
  SearchGeolocationDisclosureTabHelper::FakeShowingDisclosureForTests(
      profile());
  SetOldPreference(false /* setting */);
  ReinitializeService(true /* clear_pref */);
  EXPECT_FALSE(SearchGeolocationDisclosureTabHelper::IsDisclosureResetForTests(
      profile()));
}

TEST_F(SearchPermissionsServiceTest, IsPermissionControlledByDSE) {
  // True for origin that matches the CCTLD and meets all requirements.
  test_delegate()->ChangeDSEOrigin(kGoogleURL);
  EXPECT_TRUE(GetService()->IsPermissionControlledByDSE(
      ContentSettingsType::NOTIFICATIONS, ToOrigin(kGoogleURL)));

  // False for different origin.
  EXPECT_FALSE(GetService()->IsPermissionControlledByDSE(
      ContentSettingsType::GEOLOCATION, ToOrigin(kGoogleAusURL)));

  // False for http origin.
  test_delegate()->ChangeDSEOrigin(kGoogleHTTPURL);
  EXPECT_FALSE(GetService()->IsPermissionControlledByDSE(
      ContentSettingsType::NOTIFICATIONS, ToOrigin(kGoogleHTTPURL)));

  // True even for non-Google search engines.
  test_delegate()->ChangeDSEOrigin(kExampleURL);
  EXPECT_TRUE(GetService()->IsPermissionControlledByDSE(
      ContentSettingsType::GEOLOCATION, ToOrigin(kExampleURL)));

  // False for permissions not controlled by the DSE.
  EXPECT_FALSE(GetService()->IsPermissionControlledByDSE(
      ContentSettingsType::COOKIES, ToOrigin(kExampleURL)));

  // If autogrant is reverted, permissions are not controlled by DSE anymore.
  base::test::ScopedFeatureList features(
      permissions::features::kRevertDSEAutomaticPermissions);
  EXPECT_FALSE(GetService()->IsPermissionControlledByDSE(
      ContentSettingsType::NOTIFICATIONS, ToOrigin(kExampleURL)));
  EXPECT_FALSE(GetService()->IsPermissionControlledByDSE(
      ContentSettingsType::GEOLOCATION, ToOrigin(kExampleURL)));

  // Still applies after changing the search engine origin.
  test_delegate()->ChangeDSEOrigin(kGoogleURL);
  EXPECT_FALSE(GetService()->IsPermissionControlledByDSE(
      ContentSettingsType::NOTIFICATIONS, ToOrigin(kGoogleURL)));
  EXPECT_FALSE(GetService()->IsPermissionControlledByDSE(
      ContentSettingsType::GEOLOCATION, ToOrigin(kGoogleURL)));
}

TEST_F(SearchPermissionsServiceTest, DSEChanges) {
  test_delegate()->ChangeDSEOrigin(kGoogleURL);
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS));

  // Change to google.com.au. Settings for google.com should revert and settings
  // for google.com.au should be set to allow.
  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS));
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_ALLOW,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));

  // Set the content setting for google.com to block for notifications. When we
  // change back to google.com, the setting should still be blocked.
  SetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS,
                    CONTENT_SETTING_BLOCK);
  test_delegate()->ChangeDSEOrigin(kGoogleURL);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS));
  EXPECT_EQ(
      CONTENT_SETTING_ASK,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));

  // Now set the notification setting for google.com.au to ALLOW. When we change
  // to google.com.au notifications should still be blocked. The google.com
  // notifications setting should remain blocked.
  SetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS,
                    CONTENT_SETTING_ALLOW);
  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS));
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));

  // Now changing back to google.com, the google.com.au notifications setting
  // should be reset to ask (we reset it because of the conflict previously).
  test_delegate()->ChangeDSEOrigin(kGoogleURL);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS));
  EXPECT_EQ(
      CONTENT_SETTING_ASK,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));

  // The google.com setting was block before it became the DSE and it remains
  // block. Now, if it's toggled to allow while it's still the DSE, we should
  // reset it back to ask once it is no longer the DSE.
  SetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS,
                    CONTENT_SETTING_ALLOW);
  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS));
  EXPECT_EQ(
      CONTENT_SETTING_ALLOW,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));
}

TEST_F(SearchPermissionsServiceTest, DSEChangesWithEnterprisePolicy) {
  test_delegate()->ChangeDSEOrigin(kGoogleURL);

  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));

  // Set a policy value for the geolocation setting.
  sync_preferences::TestingPrefServiceSyncable* prefs =
      profile()->GetTestingPrefService();
  prefs->SetManagedPref(prefs::kManagedDefaultGeolocationSetting,
                        std::make_unique<base::Value>(CONTENT_SETTING_BLOCK));
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));

  // Change DSE.
  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);

  // The enterprise policy should still be in effect.
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));

  // When the enterprise policy goes away, the setting should revert to ALLOW
  // for the current DSE and ASK for the previous one.
  prefs->RemoveManagedPref(prefs::kManagedDefaultGeolocationSetting);
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));

  // Simulate the user setting google.com to BLOCK.
  SetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION,
                    CONTENT_SETTING_BLOCK);

  // Put an ALLOW enterprise policy in place.
  prefs->SetManagedPref(prefs::kManagedDefaultGeolocationSetting,
                        std::make_unique<base::Value>(CONTENT_SETTING_ALLOW));
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));

  // Now change the DSE back to google.com. The enterprise setting should still
  // be in effect so it should be ALLOW.
  test_delegate()->ChangeDSEOrigin(kGoogleURL);
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));

  // Remove the enterprise policy. google.com should go back to blocked.
  // google.com.au should be ASK.
  prefs->RemoveManagedPref(prefs::kManagedDefaultGeolocationSetting);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
}

TEST_F(SearchPermissionsServiceTest, DSEChangesAndDisclosure) {
  test_delegate()->ChangeDSEOrigin(kGoogleURL);
  SearchGeolocationDisclosureTabHelper::FakeShowingDisclosureForTests(
      profile());
  // Change to google.com.au. The disclosure should not be reset.
  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
  EXPECT_FALSE(SearchGeolocationDisclosureTabHelper::IsDisclosureResetForTests(
      profile()));

  // Now set to a non-google search. The disclosure should be reset.
  test_delegate()->ChangeDSEOrigin(kExampleURL);
  EXPECT_TRUE(SearchGeolocationDisclosureTabHelper::IsDisclosureResetForTests(
      profile()));
  SearchGeolocationDisclosureTabHelper::FakeShowingDisclosureForTests(
      profile());

  // Go back to google.com.au. The disclosure should again be reset.
  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
  EXPECT_TRUE(SearchGeolocationDisclosureTabHelper::IsDisclosureResetForTests(
      profile()));
}

TEST_F(SearchPermissionsServiceTest, Embargo) {
  test_delegate()->ChangeDSEOrigin(kGoogleURL);

  // Place another origin under embargo.
  GURL google_aus_url(kGoogleAusURL);
  permissions::PermissionDecisionAutoBlocker* auto_blocker =
      PermissionDecisionAutoBlockerFactory::GetForProfile(profile());
  auto_blocker->RecordDismissAndEmbargo(
      google_aus_url, ContentSettingsType::GEOLOCATION, false);
  auto_blocker->RecordDismissAndEmbargo(
      google_aus_url, ContentSettingsType::GEOLOCATION, false);
  auto_blocker->RecordDismissAndEmbargo(
      google_aus_url, ContentSettingsType::GEOLOCATION, false);
  permissions::PermissionResult result = auto_blocker->GetEmbargoResult(
      GURL(kGoogleAusURL), ContentSettingsType::GEOLOCATION);
  EXPECT_EQ(result.source,
            permissions::PermissionStatusSource::MULTIPLE_DISMISSALS);
  EXPECT_EQ(result.content_setting, CONTENT_SETTING_BLOCK);

  // Now change the DSE to this origin and make sure the embargo is cleared.
  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
  result = auto_blocker->GetEmbargoResult(GURL(kGoogleAusURL),
                                          ContentSettingsType::GEOLOCATION);
  EXPECT_EQ(result.source, permissions::PermissionStatusSource::UNSPECIFIED);
  EXPECT_EQ(result.content_setting, CONTENT_SETTING_ASK);
}

TEST_F(SearchPermissionsServiceTest, DSEChangedButDisabled) {
  SetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION,
                    CONTENT_SETTING_BLOCK);

  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_ALLOW,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));

  // DSE disabled by enterprise policy
  test_delegate()->ChangeDSEOrigin(std::string());

  // The settings should return to their original value.
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_ASK,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));

  // Now disable enterprise policy. The settings will be BLOCK because we don't
  // know what the user's previous DSE setting was.
  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));
}

TEST_F(SearchPermissionsServiceTest, DSEInitializedButDisabled) {
  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_ALLOW,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));

  // Set the DSE origin without calling OnDSEChanged.
  test_delegate()->set_dse_origin(std::string());

  ReinitializeService(/*clear_pref=*/false);

  // Settings should revert back to default.
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_ASK,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));

  // The pref shouldn't exist anymore.
  EXPECT_FALSE(
      profile()->GetPrefs()->HasPrefPath(prefs::kDSEPermissionsSettings));

  // Firing the DSE changed event now should not do anything.
  test_delegate()->ChangeDSEOrigin(std::string());

  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_ASK,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));

  EXPECT_FALSE(
      profile()->GetPrefs()->HasPrefPath(prefs::kDSEPermissionsSettings));

  // Re-enabling the DSE origin should set the permissions to BLOCK for safety,
  // except for notifications where the user had manually granted permission.
  SetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS,
                    CONTENT_SETTING_ALLOW);

  test_delegate()->set_dse_origin(kGoogleAusURL);
  ReinitializeService(/*clear_pref=*/false);

  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_ALLOW,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));
}

TEST_F(SearchPermissionsServiceTest, ResetDSEPermission) {
  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
  SetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION,
                    CONTENT_SETTING_BLOCK);
  SetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS,
                    CONTENT_SETTING_BLOCK);
  SetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION,
                    CONTENT_SETTING_BLOCK);

  GetService()->ResetDSEPermission(ContentSettingsType::GEOLOCATION);
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_BLOCK,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));

  GetService()->ResetDSEPermission(ContentSettingsType::NOTIFICATIONS);
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_ALLOW,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));
}

TEST_F(SearchPermissionsServiceTest, ResetDSEPermissions) {
  test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
  SetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION,
                    CONTENT_SETTING_BLOCK);
  SetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS,
                    CONTENT_SETTING_BLOCK);
  SetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION,
                    CONTENT_SETTING_BLOCK);

  GetService()->ResetDSEPermissions();
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_ALLOW,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            GetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION));

  // After enabling `kRevertDSEAutomaticPermissions` ResetDSEPermissions reverts
  // it to ASK.
  base::test::ScopedFeatureList features(
      permissions::features::kRevertDSEAutomaticPermissions);
  SetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION,
                    CONTENT_SETTING_BLOCK);
  SetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS,
                    CONTENT_SETTING_BLOCK);

  GetService()->ResetDSEPermissions();
  EXPECT_EQ(CONTENT_SETTING_ASK,
            GetContentSetting(kGoogleAusURL, ContentSettingsType::GEOLOCATION));
  EXPECT_EQ(
      CONTENT_SETTING_ASK,
      GetContentSetting(kGoogleAusURL, ContentSettingsType::NOTIFICATIONS));
}

// Setting the `RevertDSEAutomaticPermissions` feature disables DSE permissions.
TEST_F(SearchPermissionsServiceTest, DSEPermissionsCanBeDisabledByFeature) {
  constexpr struct {
    ContentSetting initial_setting;
    ContentSetting expected_setting_after_autogrant;
    ContentSetting expected_setting_after_autogrant_reverted;
  } kTests[] = {
      {CONTENT_SETTING_DEFAULT, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK},
      {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW},
      {CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK},
      {CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK},
  };

  test_delegate()->ChangeDSEOrigin(kGoogleURL);

  for (const auto& test : kTests) {
    for (const auto type : {ContentSettingsType::NOTIFICATIONS,
                            ContentSettingsType::GEOLOCATION}) {
      // Notifications can not be set to ASK.
      if (test.initial_setting == CONTENT_SETTING_ASK &&
          type == ContentSettingsType::NOTIFICATIONS) {
        continue;
      }

      ClearContentSettings(type);
      SetContentSetting(kGoogleURL, type, test.initial_setting);

      // Initialize DSE and verify the expected setting.
      ReinitializeService(true /* clear_pref */);
      EXPECT_EQ(test.expected_setting_after_autogrant,
                GetContentSetting(kGoogleURL, type));

      // Enable `RevertDSEAutomaticPermissions`. DSE stops being controlled by
      // the SearchPermissionsService.
      {
        base::test::ScopedFeatureList features(
            permissions::features::kRevertDSEAutomaticPermissions);
        ReinitializeService(false /* clear_pref */);
        EXPECT_EQ(test.expected_setting_after_autogrant_reverted,
                  GetContentSetting(kGoogleURL, type));
      }

      // If the feature is disabled again, DSE starts being controlled by the
      // SearchPermissionsService.
      ReinitializeService(false /* clear_pref */);
      EXPECT_EQ(test.expected_setting_after_autogrant,
                GetContentSetting(kGoogleURL, type));
    }
  }
}

// Change the permission state for the DSE origin while DSE is active. Also test
// how the `RevertDSEAutomaticPermissions` feature interacts with this scenario.
TEST_F(SearchPermissionsServiceTest,
       DSEOriginPermissionsChangeBeforeFeatureIsEnabled) {
  constexpr struct {
    ContentSetting initial_setting;
    ContentSetting expected_setting_after_autogrant;
    ContentSetting updated_setting;
    ContentSetting expected_setting_after_update;
    ContentSetting expected_setting_after_autogrant_reverted;
    ContentSetting expected_setting_after_autogrant_reverted_disabled;
  } kTests[] = {
      {CONTENT_SETTING_DEFAULT, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW,
       CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW},
      // Critical journey: if the user decided to change the DSE origin's
      // setting to BLOCK then disabling DSE should still keep it at BLOCK.
      {CONTENT_SETTING_DEFAULT, CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK,
       CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK},
      {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW,
       CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW},
      // Critical journey: if the user decided to change the DSE origin's
      // setting to BLOCK then disabling DSE should still keep it at BLOCK.
      {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK,
       CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK},
      {CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_ALLOW,
       CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW},
      {CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK,
       CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK},
      {CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW,
       CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW},
      // Critical journey: if the user decided to change the DSE origin's
      // setting to BLOCK then disabling DSE should still keep it at BLOCK.
      {CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK,
       CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK},
  };

  for (const auto& test : kTests) {
    for (const auto type : {ContentSettingsType::NOTIFICATIONS,
                            ContentSettingsType::GEOLOCATION}) {
      // Notifications can not be set to ASK on Android as notification channels
      // explicitly rely on the state being only BLOCK/ALLOW/DEFAULT.
      if (test.initial_setting == CONTENT_SETTING_ASK &&
          type == ContentSettingsType::NOTIFICATIONS) {
        continue;
      }

      ClearContentSettings(type);
      SetContentSetting(kGoogleURL, type, test.initial_setting);

      // Initialize DSE and verify the expected setting.
      ReinitializeService(true /* clear_pref */);
      EXPECT_EQ(test.expected_setting_after_autogrant,
                GetContentSetting(kGoogleURL, type));

      // Change the setting of the DSE origin and verify the result.
      SetContentSetting(kGoogleURL, type, test.updated_setting);
      ReinitializeService(false /* clear_pref */);
      EXPECT_EQ(test.expected_setting_after_update,
                GetContentSetting(kGoogleURL, type));

      // Enable `RevertDSEAutomaticPermissions`. DSE stops being controlled by
      // the SearchPermissionsService.
      {
        base::test::ScopedFeatureList features(
            permissions::features::kRevertDSEAutomaticPermissions);
        ReinitializeService(false /* clear_pref */);
        EXPECT_EQ(test.expected_setting_after_autogrant_reverted,
                  GetContentSetting(kGoogleURL, type));
      }

      // If the feature is disabled again, DSE starts being controlled by the
      // SearchPermissionsService.
      ReinitializeService(false /* clear_pref */);
      EXPECT_EQ(test.expected_setting_after_autogrant_reverted_disabled,
                GetContentSetting(kGoogleURL, type));
    }
  }
}

// Change the permission state for the DSE origin while the
// `RevertDSEAutomaticPermissions` feature is active. Also test how disabling
// the feature again afterwards will affect the permission.
TEST_F(SearchPermissionsServiceTest,
       DSEOriginPermissionsChangeAfterFeatureIsEnabled) {
  constexpr struct {
    ContentSetting initial_setting;
    ContentSetting expected_setting_after_autogrant;
    ContentSetting expected_setting_after_autogrant_reverted;
  } kTestsBeforeSettingUpdate[] = {
      {CONTENT_SETTING_DEFAULT, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK},
      {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW},
      {CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK},
      {CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK},
  };

  constexpr struct {
    ContentSetting updated_setting;
    ContentSetting expected_setting_after_update;
    ContentSetting expected_setting_after_autogrant_reverted_disabled;
  } kTestsAfterSettingUpdate[] = {
      {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW},
      {CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK},
      {CONTENT_SETTING_ASK, CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW},
      {CONTENT_SETTING_DEFAULT, CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW},
  };

  // The test cases are split into two halves. Every combination of
  // `kTestsBeforeSettingUpdate` and `kTestsBeforeSettingUpdate` entries
  // produces a test case.
  for (const auto& test_before : kTestsBeforeSettingUpdate) {
    for (const auto& test_after : kTestsAfterSettingUpdate) {
      for (const auto type : {ContentSettingsType::NOTIFICATIONS,
                              ContentSettingsType::GEOLOCATION}) {
        // Notifications can not be set to ASK on Android as notification
        // channels explicitly rely on the state being only BLOCK/ALLOW/DEFAULT.
        if (test_before.initial_setting == CONTENT_SETTING_ASK &&
            type == ContentSettingsType::NOTIFICATIONS) {
          continue;
        }

        ClearContentSettings(type);
        SetContentSetting(kGoogleURL, type, test_before.initial_setting);

        // Initialize DSE and verify the expected setting.
        ReinitializeService(true /* clear_pref */);
        EXPECT_EQ(test_before.expected_setting_after_autogrant,
                  GetContentSetting(kGoogleURL, type));

        // Enable `RevertDSEAutomaticPermissions`. DSE stops being controlled by
        // the SearchPermissionsService.
        {
          base::test::ScopedFeatureList features(
              permissions::features::kRevertDSEAutomaticPermissions);
          ReinitializeService(false /* clear_pref */);
          EXPECT_EQ(test_before.expected_setting_after_autogrant_reverted,
                    GetContentSetting(kGoogleURL, type));

          // Notifications can not be set to ASK on Android as notification
          // channels explicitly rely on the state being only
          // BLOCK/ALLOW/DEFAULT.
          if (test_after.updated_setting == CONTENT_SETTING_ASK &&
              type == ContentSettingsType::NOTIFICATIONS) {
            continue;
          }

          // Change the setting of the DSE origin and verify the result. This
          // means that the user made a decision for the DSE origin while DSE
          // is inactive.
          SetContentSetting(kGoogleURL, type, test_after.updated_setting);
          ReinitializeService(false /* clear_pref */);
          EXPECT_EQ(test_after.expected_setting_after_update,
                    GetContentSetting(kGoogleURL, type));
        }

        // If the feature is disabled again, DSE starts being controlled by the
        // SearchPermissionsService. Test how it handles the user's setting
        // change while DSE was inactive.
        ReinitializeService(false /* clear_pref */);
        EXPECT_EQ(test_after.expected_setting_after_autogrant_reverted_disabled,
                  GetContentSetting(kGoogleURL, type));
      }
    }
  }
}

// Tests the scenario in which the permission is disabled by default for all
// origins.
TEST_F(SearchPermissionsServiceTest, PermissionsDisabledByDefault) {
  HostContentSettingsMap* hcsm =
      HostContentSettingsMapFactory::GetForProfile(profile());

  for (const auto type :
       {ContentSettingsType::GEOLOCATION, ContentSettingsType::NOTIFICATIONS}) {
    ClearContentSettings(type);
    hcsm->SetDefaultContentSetting(type, CONTENT_SETTING_BLOCK);

    // Global setting should still apply.
    ReinitializeService(true /* clear_pref */);
    EXPECT_EQ(CONTENT_SETTING_BLOCK, GetContentSetting(kGoogleURL, type));

    // Enable `RevertDSEAutomaticPermissions`. DSE stops being controlled by the
    // SearchPermissionsService.
    {
      base::test::ScopedFeatureList features(
          permissions::features::kRevertDSEAutomaticPermissions);
      ReinitializeService(false /* clear_pref */);
      EXPECT_EQ(CONTENT_SETTING_BLOCK, GetContentSetting(kGoogleURL, type));
    }

    // If the feature is disabled again, DSE starts being controlled by the
    // SearchPermissionsService.
    ReinitializeService(false /* clear_pref */);
    EXPECT_EQ(CONTENT_SETTING_BLOCK, GetContentSetting(kGoogleURL, type));
  }
}

// Tests the scenario in which the permission is disabled by default for all
// origins after DSE has granted permission.
TEST_F(SearchPermissionsServiceTest,
       PermissionsDisabledByDefaultAfterAutogrant) {
  HostContentSettingsMap* hcsm =
      HostContentSettingsMapFactory::GetForProfile(profile());

  for (const auto type :
       {ContentSettingsType::GEOLOCATION, ContentSettingsType::NOTIFICATIONS}) {
    ClearContentSettings(type);
    ReinitializeService(true /* clear_pref */);
    EXPECT_EQ(CONTENT_SETTING_ALLOW, GetContentSetting(kGoogleURL, type));

    // Set the default content setting after the DSE has been applied.
    hcsm->SetDefaultContentSetting(type, CONTENT_SETTING_BLOCK);
    EXPECT_EQ(CONTENT_SETTING_ALLOW, GetContentSetting(kGoogleURL, type));

    // Enable `RevertDSEAutomaticPermissions`. DSE stops being controlled by the
    // SearchPermissionsService.
    {
      base::test::ScopedFeatureList features(
          permissions::features::kRevertDSEAutomaticPermissions);
      ReinitializeService(false /* clear_pref */);
      EXPECT_EQ(CONTENT_SETTING_BLOCK, GetContentSetting(kGoogleURL, type));
    }

    // If the feature is disabled again, DSE starts being controlled by the
    // SearchPermissionsService.
    ReinitializeService(false /* clear_pref */);
    EXPECT_EQ(CONTENT_SETTING_BLOCK, GetContentSetting(kGoogleURL, type));
  }
}

// Test repeatedly enabling and disabling the `RevertDSEAutomaticPermissions`
// feature.
TEST_F(SearchPermissionsServiceTest,
       DSEPermissionsRepeatedlyDisabledByFeature) {
  for (const auto type :
       {ContentSettingsType::GEOLOCATION, ContentSettingsType::NOTIFICATIONS}) {
    ClearContentSettings(type);
    ReinitializeService(true /* clear_pref */);
    EXPECT_EQ(CONTENT_SETTING_ALLOW, GetContentSetting(kGoogleURL, type));

    int num_repetitions = 5;
    while (num_repetitions--) {
      // Enable `RevertDSEAutomaticPermissions`. DSE stops being controlled by
      // the SearchPermissionsService.
      {
        base::test::ScopedFeatureList features(
            permissions::features::kRevertDSEAutomaticPermissions);
        ReinitializeService(false /* clear_pref */);
        EXPECT_EQ(CONTENT_SETTING_ASK, GetContentSetting(kGoogleURL, type));
      }

      // If the feature is disabled again, DSE starts being controlled by the
      // SearchPermissionsService.
      ReinitializeService(false /* clear_pref */);
      EXPECT_EQ(CONTENT_SETTING_ALLOW, GetContentSetting(kGoogleURL, type));
    }
  }
}

// Test disabling DSE via the `RevertDSEAutomaticPermissions` feature after the
// DSE origin has changed at least once.
TEST_F(SearchPermissionsServiceTest,
       AutoDSEPermissionRevertedAfterOriginChange) {
  for (const auto type :
       {ContentSettingsType::GEOLOCATION, ContentSettingsType::NOTIFICATIONS}) {
    test_delegate()->ChangeDSEOrigin(kGoogleURL);
    ClearContentSettings(type);
    ReinitializeService(true /* clear_pref */);
    EXPECT_EQ(CONTENT_SETTING_ALLOW, GetContentSetting(kGoogleURL, type));

    // Change DSE origin and make sure the setting is correctly updated.
    test_delegate()->ChangeDSEOrigin(kGoogleAusURL);
    EXPECT_EQ(CONTENT_SETTING_ALLOW, GetContentSetting(kGoogleAusURL, type));
    EXPECT_EQ(CONTENT_SETTING_ASK, GetContentSetting(kGoogleURL, type));

    // Enable `RevertDSEAutomaticPermissions`. DSE stops being controlled by
    // the SearchPermissionsService.
    {
      base::test::ScopedFeatureList features(
          permissions::features::kRevertDSEAutomaticPermissions);
      ReinitializeService(false /* clear_pref */);
      EXPECT_EQ(CONTENT_SETTING_ASK, GetContentSetting(kGoogleAusURL, type));
      EXPECT_EQ(CONTENT_SETTING_ASK, GetContentSetting(kGoogleURL, type));
    }

    // If the feature is disabled again, DSE starts being controlled by the
    // SearchPermissionsService.
    ReinitializeService(false /* clear_pref */);
    EXPECT_EQ(CONTENT_SETTING_ALLOW, GetContentSetting(kGoogleAusURL, type));
    EXPECT_EQ(CONTENT_SETTING_ASK, GetContentSetting(kGoogleURL, type));
  }
}

// Test that the appropriate UMA metrics have been recorded when the DSE is
// disabled.
TEST_F(SearchPermissionsServiceTest,
       MetricsAndPrefsAreRecordedWhenAutoDSEPermissionReverted) {
  constexpr struct {
    ContentSetting initial_setting;
    ContentSetting updated_setting;
    permissions::AutoDSEPermissionRevertTransition expected_transition;
  } kTests[] = {
      {CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW,
       permissions::AutoDSEPermissionRevertTransition::NO_DECISION_ASK},
      {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW,
       permissions::AutoDSEPermissionRevertTransition::PRESERVE_ALLOW},
      {CONTENT_SETTING_BLOCK, CONTENT_SETTING_ALLOW,
       permissions::AutoDSEPermissionRevertTransition::CONFLICT_ASK},
      {CONTENT_SETTING_ASK, CONTENT_SETTING_BLOCK,
       permissions::AutoDSEPermissionRevertTransition::PRESERVE_BLOCK_ASK},
      {CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK,
       permissions::AutoDSEPermissionRevertTransition::PRESERVE_BLOCK_ALLOW},
      {CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK,
       permissions::AutoDSEPermissionRevertTransition::PRESERVE_BLOCK_BLOCK},
  };

  for (const auto& test : kTests) {
    for (const auto& type : {ContentSettingsType::NOTIFICATIONS,
                             ContentSettingsType::GEOLOCATION}) {
      // Notifications can not be set to ASK on Android as notification channels
      // explicitly rely on the state being only BLOCK/ALLOW/DEFAULT.
      if (test.initial_setting == CONTENT_SETTING_ASK &&
          type == ContentSettingsType::NOTIFICATIONS) {
        continue;
      }

      ClearContentSettings(type);
      SetContentSetting(kGoogleURL, type, test.initial_setting);
      ReinitializeService(true /* clear_pref */);
      SetContentSetting(kGoogleURL, type, test.updated_setting);
      ReinitializeService(false /* clear_pref */);

      // Enable `RevertDSEAutomaticPermissions`. DSE stops being controlled by
      // the SearchPermissionsService.
      {
        base::HistogramTester histograms;
        base::test::ScopedFeatureList features(
            permissions::features::kRevertDSEAutomaticPermissions);
        ReinitializeService(false /* clear_pref */);

        // Test that the expected samples are recorded in histograms.
        for (auto sample = static_cast<int>(
                 permissions::AutoDSEPermissionRevertTransition::
                     NO_DECISION_ASK);
             sample <
             static_cast<int>(
                 permissions::AutoDSEPermissionRevertTransition::kMaxValue);
             ++sample) {
          std::string histogram =
              "Permissions.DSE.AutoPermissionRevertTransition.";
          histogram += type == ContentSettingsType::NOTIFICATIONS
                           ? "Notifications"
                           : "Geolocation";
          histograms.ExpectBucketCount(
              histogram, sample,
              static_cast<int>(test.expected_transition) == sample ? 1 : 0);
        }
      }

      // If DSE starts being controlled again by the `SearchPermissionService`,
      // the pref is cleared.
      ReinitializeService(false /* clear_pref */);
    }
  }
}

// Records DSE origin settings whenever the service is initialized.
TEST_F(SearchPermissionsServiceTest, DSEEffectiveSettingMetric) {
  base::HistogramTester histograms;
  ClearContentSettings(ContentSettingsType::NOTIFICATIONS);
  ClearContentSettings(ContentSettingsType::GEOLOCATION);
  ReinitializeService(true /* clear_pref */);
  histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Notifications",
                               CONTENT_SETTING_ALLOW, 1);
  histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Geolocation",
                               CONTENT_SETTING_ALLOW, 1);

  SetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS,
                    CONTENT_SETTING_BLOCK);
  ReinitializeService(false /* clear_pref */);
  histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Notifications",
                               CONTENT_SETTING_ALLOW, 1);
  histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Notifications",
                               CONTENT_SETTING_BLOCK, 1);
  histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Geolocation",
                               CONTENT_SETTING_ALLOW, 2);

  base::test::ScopedFeatureList features(
      permissions::features::kRevertDSEAutomaticPermissions);
  ReinitializeService(false /* clear_pref */);

  histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Notifications",
                               CONTENT_SETTING_ALLOW, 1);
  histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Notifications",
                               CONTENT_SETTING_BLOCK, 2);
  histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Geolocation",
                               CONTENT_SETTING_ALLOW, 2);
  histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Geolocation",
                               CONTENT_SETTING_ASK, 1);
}


text:
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('print_preview', function() {
  'use strict';

  /**
   * Creates a Margins object that holds four margin values in points.
   * @param {number} top The top margin in pts.
   * @param {number} right The right margin in pts.
   * @param {number} bottom The bottom margin in pts.
   * @param {number} left The left margin in pts.
   * @constructor
   */
  function Margins(top, right, bottom, left) {
    /**
     * Backing store for the margin values in points.
     * @type {!Object<
     *     !print_preview.ticket_items.CustomMargins.Orientation, number>}
     * @private
     */
    this.value_ = {};
    this.value_[print_preview.ticket_items.CustomMargins.Orientation.TOP] = top;
    this.value_[print_preview.ticket_items.CustomMargins.Orientation.RIGHT] =
        right;
    this.value_[print_preview.ticket_items.CustomMargins.Orientation.BOTTOM] =
        bottom;
    this.value_[print_preview.ticket_items.CustomMargins.Orientation.LEFT] =
        left;
  };

  /**
   * Parses a margins object from the given serialized state.
   * @param {Object} state Serialized representation of the margins created by
   *     the {@code serialize} method.
   * @return {!print_preview.Margins} New margins instance.
   */
  Margins.parse = function(state) {
    return new print_preview.Margins(
        state[print_preview.ticket_items.CustomMargins.Orientation.TOP] || 0,
        state[print_preview.ticket_items.CustomMargins.Orientation.RIGHT] || 0,
        state[print_preview.ticket_items.CustomMargins.Orientation.BOTTOM] || 0,
        state[print_preview.ticket_items.CustomMargins.Orientation.LEFT] || 0);
  };

  Margins.prototype = {
    /**
     * @param {!print_preview.ticket_items.CustomMargins.Orientation}
     *     orientation Specifies the margin value to get.
     * @return {number} Value of the margin of the given orientation.
     */
    get: function(orientation) {
      return this.value_[orientation];
    },

    /**
     * @param {!print_preview.ticket_items.CustomMargins.Orientation}
     *     orientation Specifies the margin to set.
     * @param {number} value Updated value of the margin in points to modify.
     * @return {!print_preview.Margins} A new copy of |this| with the
     *     modification made to the specified margin.
     */
    set: function(orientation, value) {
      var newValue = this.clone_();
      newValue[orientation] = value;
      return new Margins(
          newValue[print_preview.ticket_items.CustomMargins.Orientation.TOP],
          newValue[print_preview.ticket_items.CustomMargins.Orientation.RIGHT],
          newValue[print_preview.ticket_items.CustomMargins.Orientation.BOTTOM],
          newValue[print_preview.ticket_items.CustomMargins.Orientation.LEFT]);
    },

    /**
     * @param {print_preview.Margins} other The other margins object to compare
     *     against.
     * @return {boolean} Whether this margins object is equal to another.
     */
    equals: function(other) {
      if (other == null) {
        return false;
      }
      for (var orientation in this.value_) {
        if (this.value_[orientation] != other.value_[orientation]) {
          return false;
        }
      }
      return true;
    },

    /** @return {Object} A serialized representation of the margins. */
    serialize: function() {
      return this.clone_();
    },

    /**
     * @return {Object} Cloned state of the margins.
     * @private
     */
    clone_: function() {
      var clone = {};
      for (var o in this.value_) {
        clone[o] = this.value_[o];
      }
      return clone;
    }
  };

  // Export
  return {
    Margins: Margins
  };
});


text:
package coursier.bootstrap.launcher.jar;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;

/**
 * A ZIP File "Central directory file header record" (CDFH).
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @see Zip File Format
 */

final class CentralDirectoryFileHeader implements FileHeader {

    private static final AsciiBytes SLASH = new AsciiBytes("/");

    private static final byte[] NO_EXTRA = {};

    private static final AsciiBytes NO_COMMENT = new AsciiBytes("");

    private byte[] header;

    private int headerOffset;

    private AsciiBytes name;

    private byte[] extra;

    private AsciiBytes comment;

    private long localHeaderOffset;

    CentralDirectoryFileHeader() {
    }

    CentralDirectoryFileHeader(byte[] header, int headerOffset, AsciiBytes name,
            byte[] extra, AsciiBytes comment, long localHeaderOffset) {
        this.header = header;
        this.headerOffset = headerOffset;
        this.name = name;
        this.extra = extra;
        this.comment = comment;
        this.localHeaderOffset = localHeaderOffset;
    }

    void load(byte[] data, int dataOffset, RandomAccessData variableData,
            int variableOffset, JarEntryFilter filter) throws IOException {
        // Load fixed part
        this.header = data;
        this.headerOffset = dataOffset;
        long nameLength = Bytes.littleEndianValue(data, dataOffset + 28, 2);
        long extraLength = Bytes.littleEndianValue(data, dataOffset + 30, 2);
        long commentLength = Bytes.littleEndianValue(data, dataOffset + 32, 2);
        this.localHeaderOffset = Bytes.littleEndianValue(data, dataOffset + 42, 4);
        // Load variable part
        dataOffset += 46;
        if (variableData != null) {
            data = variableData.read(variableOffset + 46,
                    nameLength + extraLength + commentLength);
            dataOffset = 0;
        }
        this.name = new AsciiBytes(data, dataOffset, (int) nameLength);
        if (filter != null) {
            this.name = filter.apply(this.name);
        }
        this.extra = NO_EXTRA;
        this.comment = NO_COMMENT;
        if (extraLength > 0) {
            this.extra = new byte[(int) extraLength];
            System.arraycopy(data, (int) (dataOffset + nameLength), this.extra, 0,
                    this.extra.length);
        }
        if (commentLength > 0) {
            this.comment = new AsciiBytes(data,
                    (int) (dataOffset + nameLength + extraLength), (int) commentLength);
        }
    }

    public AsciiBytes getName() {
        return this.name;
    }

    @Override
    public boolean hasName(CharSequence name, char suffix) {
        return this.name.matches(name, suffix);
    }

    public boolean isDirectory() {
        return this.name.endsWith(SLASH);
    }

    @Override
    public int getMethod() {
        return (int) Bytes.littleEndianValue(this.header, this.headerOffset + 10, 2);
    }

    public long getTime() {
        long datetime = Bytes.littleEndianValue(this.header, this.headerOffset + 12, 4);
        return decodeMsDosFormatDateTime(datetime);
    }

    /**
     * Decode MS-DOS Date Time details. See
     * mindprod.com/jgloss/zip.html for
     * more details of the format.
     * @param datetime the date and time
     * @return the date and time as milliseconds since the epoch
     */
    private long decodeMsDosFormatDateTime(long datetime) {
        LocalDateTime localDateTime = LocalDateTime.of(
                (int) (((datetime >> 25) & 0x7f) + 1980), (int) ((datetime >> 21) & 0x0f),
                (int) ((datetime >> 16) & 0x1f), (int) ((datetime >> 11) & 0x1f),
                (int) ((datetime >> 5) & 0x3f), (int) ((datetime << 1) & 0x3e));
        return localDateTime.toEpochSecond(
                ZoneId.systemDefault().getRules().getOffset(localDateTime)) * 1000;
    }

    public long getCrc() {
        return Bytes.littleEndianValue(this.header, this.headerOffset + 16, 4);
    }

    @Override
    public long getCompressedSize() {
        return Bytes.littleEndianValue(this.header, this.headerOffset + 20, 4);
    }

    @Override
    public long getSize() {
        return Bytes.littleEndianValue(this.header, this.headerOffset + 24, 4);
    }

    public byte[] getExtra() {
        return this.extra;
    }

    public AsciiBytes getComment() {
        return this.comment;
    }

    @Override
    public long getLocalHeaderOffset() {
        return this.localHeaderOffset;
    }

    @Override
    public CentralDirectoryFileHeader clone() {
        byte[] header = new byte[46];
        System.arraycopy(this.header, this.headerOffset, header, 0, header.length);
        return new CentralDirectoryFileHeader(header, 0, this.name, header, this.comment,
                this.localHeaderOffset);
    }

    public static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data,
            int offset, JarEntryFilter filter) throws IOException {
        CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader();
        byte[] bytes = data.read(offset, 46);
        fileHeader.load(bytes, 0, data, offset, filter);
        return fileHeader;
    }

}


text:
package org.hisp.dhis.scheduling;

import static org.hisp.dhis.setting.SystemSettingManager.KEY_SEND_MESSAGE_SCHEDULED_TASKS;
import static org.hisp.dhis.system.scheduling.Scheduler.STATUS_NOT_STARTED;

import java.util.HashMap;
import java.util.Map;

import org.hisp.dhis.setting.SystemSettingManager;
import org.hisp.dhis.system.scheduling.Scheduler;

/**
 * @author Chau Thu Tran
 *
 * @version DefaultProgramSchedulingManager.java 12:51:02 PM Sep 10, 2012 $
 */
public class DefaultProgramSchedulingManager implements ProgramSchedulingManager
{
    // -------------------------------------------------------------------------
    // Dependencies
    // -------------------------------------------------------------------------

    private SystemSettingManager systemSettingManager;

    public void setSystemSettingManager( SystemSettingManager systemSettingManager )
    {
        this.systemSettingManager = systemSettingManager;
    }

    private Scheduler scheduler;

    public void setScheduler( Scheduler scheduler )
    {
        this.scheduler = scheduler;
    }

    private Map<String, Runnable> tasks = new HashMap<>();

    public void setTasks( Map<String, Runnable> tasks )
    {
        this.tasks = tasks;
    }

    // -------------------------------------------------------------------------
    // SchedulingManager implementation
    // -------------------------------------------------------------------------

    @Override
    public void scheduleTasks()
    {
        Map<String, String> keyCronMap = getScheduledTasks();

        for ( String key : keyCronMap.keySet() )
        {
            String cron = keyCronMap.get( key );
            Runnable task = tasks.get( key );

            if ( cron != null && task != null )
            {
                scheduler.scheduleTask( key, task, cron );
            }
        }
    }

    @Override
    public void scheduleTasks( Map<String, String> keyCronMap )
    {
        systemSettingManager.saveSystemSetting( KEY_SEND_MESSAGE_SCHEDULED_TASKS, new HashMap<>( keyCronMap ) );

        scheduleTasks();
    }

    @Override
    public void stopTasks()
    {
        systemSettingManager.saveSystemSetting( KEY_SEND_MESSAGE_SCHEDULED_TASKS, null );

        scheduler.stopAllTasks();
    }

    @Override
    public void executeTasks()
    {
        Map<String, String> keyCronMap = getScheduledTasks();

        for ( String key : keyCronMap.keySet() )
        {
            Runnable task = tasks.get( key );

            if ( task != null )
            {
                scheduler.executeTask( task );
            }
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Map<String, String> getScheduledTasks()
    {
        return (Map<String, String>) systemSettingManager.getSystemSetting( KEY_SEND_MESSAGE_SCHEDULED_TASKS, new HashMap<String, String>() );
    }

    @Override
    public String getTaskStatus()
    {
        Map<String, String> keyCronMap = getScheduledTasks();

        if ( keyCronMap.size() == 0 )
        {
            return STATUS_NOT_STARTED;
        }

        return scheduler.getTaskStatus( keyCronMap.keySet().iterator().next() );
    }
}


text:
package com.pushinginertia.commons.net;

import org.junit.Assert;
import org.junit.Test;

import java.util.List;

import static org.junit.Assert.assertEquals;

public class IpAddressUtilsTest {
	@Test(expected = IllegalArgumentException.class)
	public void toIpNumberNull() {
		IpAddressUtils.toIpNumber(null);
	}

	@Test
	public void toIpNumber() {
		assertEquals(3401532416L, IpAddressUtils.toIpNumber("202.191.68.0"));
		assertEquals(3401190660L, IpAddressUtils.toIpNumber("202.186.13.4"));
		assertEquals(4294967295L, IpAddressUtils.toIpNumber("255.255.255.255"));
	}

	@Test
	public void toIpAddress() {
		assertEquals("202.191.68.0", IpAddressUtils.toIpAddress(3401532416L));
		assertEquals("202.186.13.4", IpAddressUtils.toIpAddress(3401190660L));
	}

	@Test
	public void toCidrNotationList1() {
		final List<String> l = IpAddressUtils.toCidrNotationList("67.117.201.128", "67.117.201.143");
		assertEquals(1, l.size());
		assertEquals("67.117.201.128/28", l.get(0));
	}

	@Test
	public void toCidrNotationList2() {
		final List<String> l = IpAddressUtils.toCidrNotationList("1.1.1.111", "1.1.1.120");
		assertEquals(3, l.size());
		assertEquals("1.1.1.111/32", l.get(0));
		assertEquals("1.1.1.112/29", l.get(1));
		assertEquals("1.1.1.120/32", l.get(2));
	}

	@Test
	public void isNonRoutable() {
		Assert.assertTrue(IpAddressUtils.isNonRoutable(new IpAddress("172.16.0.0")));
		Assert.assertTrue(IpAddressUtils.isNonRoutable(new IpAddress("172.31.255.255")));
		Assert.assertTrue(IpAddressUtils.isNonRoutable(new IpAddress("172.22.0.11")));
		Assert.assertFalse(IpAddressUtils.isNonRoutable(new IpAddress("8.8.8.8")));
		Assert.assertFalse(IpAddressUtils.isNonRoutable(new IpAddress("189.125.121.50")));
	}
}


text:
package com.fasterxml.aalto.impl;

import javax.xml.stream.*;

/**
 * Base class for all {@link XMLStreamException} instances that
 * we use. Sometimes used as is, but usually there should be more
 * specific sub-class that indicates class of exception.
 */
@SuppressWarnings("serial")
public class StreamExceptionBase
    extends XMLStreamException
{
    /**
     * D'oh. Super-class munges and hides the message, have to duplicate here
     */
    final String mMsg;

    public StreamExceptionBase(String msg) {
        super(msg);
        mMsg = msg;
    }

    public StreamExceptionBase(Throwable th) {
        super(th.getMessage(), th);
        mMsg = th.getMessage();

        if (getCause() == null) {
            initCause(th);
        }
    }

    public StreamExceptionBase(String msg, Location loc) {
        super(msg, loc);
        mMsg = msg;
    }

    public StreamExceptionBase(String msg, Location loc, Throwable th) {
        super(msg, loc, th);
        mMsg = msg;

        if (th != null && getCause() == null) {
            initCause(th);
        }
    }

    /**
     * Method is overridden for two main reasons: first, default method
     * does not display public/system id information, even if it exists, and
     * second, default implementation can not handle nested Location
     * information.
     */
    @Override
    public String getMessage()
    {
        String locMsg = getLocationDesc();
        /* Better not use super's message if we do have location information,
         * since parent's message contains (part of) Location
         * info; something we can regenerate better...
         */
        if (locMsg == null) {
            return super.getMessage();
        }
        StringBuilder sb = new StringBuilder(mMsg.length() + locMsg.length() + 20);
        sb.append(mMsg);
        sb.append('\n');
        sb.append(" at ");
        sb.append(locMsg);
        return sb.toString();
    }

    @Override
    public String toString() {
        return getClass().getName()+": "+getMessage();
    }

    /*
    /**********************************************************************
    /* Internal methods
    /**********************************************************************
     */

    protected String getLocationDesc()
    {
        Location loc = getLocation();
        return (loc == null) ? null : loc.toString();
    }
}


text:
<?php

defined('C5_EXECUTE') or die("Access Denied.");

abstract class PageCache extends Concrete5_Library_PageCache {

	public function shouldAddToCache(View $v) {

		if(IS_MOBILE){
			// Since mobile view looks different, don't cache here
			return false;
		}

		$c = $v->getCollectionObject();
		if (!is_object($c)) {
			return false;
		}

		$cp = new Permissions($c);
		if (!$cp->canViewPage()) {
			return false;
		}

		$u = new User();

		$allowedControllerActions = array('view');
		if (is_object($v->controller)) {
			if (!in_array($v->controller->getTask(), $allowedControllerActions)) {
				return false;
			}
		}

		if (!$c->getCollectionFullPageCaching()) {
			return false;
		}

		if ($u->isRegistered() || $_SERVER['REQUEST_METHOD'] == 'POST') {
			return false;
		}

		if ($c->isGeneratedCollection()) {
			if ((is_object($v->controller) && (!$v->controller->supportsPageCache())) || (!is_object($v->controller))) {
				return false;
			}
		}

		if ($c->getCollectionFullPageCaching() == 1 || FULL_PAGE_CACHE_GLOBAL === 'all') {
			// this cache page at the page level
			// this overrides any global settings
			return true;
		}

		if (FULL_PAGE_CACHE_GLOBAL !== 'blocks') {
			// we are NOT specifically caching this page, and we don't
			return false;
		}

		$blocks = $c->getBlocks();
		array_merge($c->getGlobalBlocks(), $blocks);

		foreach($blocks as $b) {
			$controller = $b->getInstance();
			if (!$controller->cacheBlockOutput()) {
				return false;
			}
		}
		return true;
	}

}


text:
package colossus
package protocols.redis

import core.DataBuffer

import akka.util.{ByteString, ByteStringBuilder}

import UnifiedProtocol._
import UnifiedBuilder._

sealed trait Reply {
  def raw: DataBuffer
}

sealed abstract class MessageReply(message: String) extends Reply {
  def prefix: ByteString

  def raw = {
    val b = new ByteStringBuilder
    b.sizeHint(message.size + 3)
    b append prefix
    b putBytes message.getBytes
    b append RN
    DataBuffer(b.result)
  }
}

case class StatusReply(message: String) extends MessageReply(message) {
  def prefix = STATUS_REPLY
  override def toString = "StatusReply(" + message + ")"
}

case class ErrorReply(message: String)  extends MessageReply(message) {
  def prefix = ERROR_REPLY
  override def toString = "ErrorReply(" + message + ")"
}

case class IntegerReply(value: Long) extends Reply {
  def raw = DataBuffer(INTEGER_REPLY ++ ByteString(value.toString) ++ RN)
}
case class BulkReply(data: ByteString) extends Reply {
  def raw = {
    val b = new ByteStringBuilder
    buildArg(data, b)
    DataBuffer(b.result)
  }
  override def toString = "BulkReply(" + data.utf8String + ")"
}
case class MBulkReply(replies: Seq[Reply]) extends Reply {
  def raw = {
    val b = new ByteStringBuilder
    b append MBULK_REPLY
    b append ByteString(replies.size.toString)
    b append RN
    replies.foreach{reply => b append ByteString(reply.raw.takeAll)} //TODO no raw data access!
    DataBuffer(b.result)
  }
}

case object EmptyMBulkReply extends Reply {
  def raw = DataBuffer(EMPTY_MBULK_REPLY)
}

case object NilReply extends Reply {
  def raw = DataBuffer(NIL_REPLY)
}

case object NilMBulkReply extends Reply {
  def raw = DataBuffer(NIL_MBULK_REPLY)
}


text:
export interface SkyOverlayConfig {
  /**
   * Specifies if the overlay should be closed after a navigation change.
   */
  closeOnNavigation?: boolean;

  /**
   * Specifies if the overlay should be closed when a user clicks outside the overlay's content.
   */
  enableClose?: boolean;

  /**
   * Specifies if mouse interactions should be allowed below the backdrop.
   */
  enablePointerEvents?: boolean;

  /**
   * Specifies if window scrolling should be enabled when the overlay is opened.
   */
  enableScroll?: boolean;

  /**
   * Specifies if the overlay's backdrop should be visible.
   */
  showBackdrop?: boolean;

  /**
   * Specifies extra CSS classes to be added to the overlay's wrapper element.
   */
  wrapperClass?: string;
}