1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6'''python %prog [options] platform chromium_os_flag template
7
8platform specifies which platform source is being generated for
9  and can be one of (win, mac, linux)
10chromium_os_flag should be 1 if this is a Chromium OS build
11template is the path to a .json policy template file.'''
12
13from __future__ import with_statement
14from functools import partial
15import json
16from optparse import OptionParser
17import re
18import sys
19import textwrap
20import types
21
22
23CHROME_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Google\\\\Chrome'
24CHROMIUM_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Chromium'
25
26
27class PolicyDetails:
28  """Parses a policy template and caches all its details."""
29
30  # Maps policy types to a tuple with 3 other types:
31  # - the equivalent base::Value::Type or 'TYPE_EXTERNAL' if the policy
32  #   references external data
33  # - the equivalent Protobuf field type
34  # - the name of one of the protobufs for shared policy types
35  # TODO(joaodasilva): refactor the 'dict' type into a more generic 'json' type
36  # that can also be used to represent lists of other JSON objects.
37  TYPE_MAP = {
38    'dict':             ('TYPE_DICTIONARY',   'string',       'String'),
39    'external':         ('TYPE_EXTERNAL',     'string',       'String'),
40    'int':              ('TYPE_INTEGER',      'int64',        'Integer'),
41    'int-enum':         ('TYPE_INTEGER',      'int64',        'Integer'),
42    'list':             ('TYPE_LIST',         'StringList',   'StringList'),
43    'main':             ('TYPE_BOOLEAN',      'bool',         'Boolean'),
44    'string':           ('TYPE_STRING',       'string',       'String'),
45    'string-enum':      ('TYPE_STRING',       'string',       'String'),
46    'string-enum-list': ('TYPE_LIST',         'StringList',   'StringList'),
47  }
48
49  class EnumItem:
50    def __init__(self, item):
51      self.caption = PolicyDetails._RemovePlaceholders(item['caption'])
52      self.value = item['value']
53
54  def __init__(self, policy, os, is_chromium_os):
55    self.id = policy['id']
56    self.name = policy['name']
57    features = policy.get('features', {})
58    self.can_be_recommended = features.get('can_be_recommended', False)
59    self.can_be_mandatory = features.get('can_be_mandatory', True)
60    self.is_deprecated = policy.get('deprecated', False)
61    self.is_device_only = policy.get('device_only', False)
62    self.schema = policy.get('schema', {})
63    self.has_enterprise_default = 'default_for_enterprise_users' in policy
64    if self.has_enterprise_default:
65      self.enterprise_default = policy['default_for_enterprise_users']
66
67    expected_platform = 'chrome_os' if is_chromium_os else os.lower()
68    self.platforms = []
69    for platform, version in [ p.split(':') for p in policy['supported_on'] ]:
70      if not version.endswith('-'):
71        continue
72
73      if platform.startswith('chrome.'):
74        platform_sub = platform[7:]
75        if platform_sub == '*':
76          self.platforms.extend(['win', 'mac', 'linux'])
77        else:
78          self.platforms.append(platform_sub)
79      else:
80        self.platforms.append(platform)
81
82    self.platforms.sort()
83    self.is_supported = expected_platform in self.platforms
84
85    if not PolicyDetails.TYPE_MAP.has_key(policy['type']):
86      raise NotImplementedError('Unknown policy type for %s: %s' %
87                                (policy['name'], policy['type']))
88    self.policy_type, self.protobuf_type, self.policy_protobuf_type = \
89        PolicyDetails.TYPE_MAP[policy['type']]
90    self.schema = policy['schema']
91
92    self.desc = '\n'.join(
93        map(str.strip,
94            PolicyDetails._RemovePlaceholders(policy['desc']).splitlines()))
95    self.caption = PolicyDetails._RemovePlaceholders(policy['caption'])
96    self.max_size = policy.get('max_size', 0)
97
98    items = policy.get('items')
99    if items is None:
100      self.items = None
101    else:
102      self.items = [ PolicyDetails.EnumItem(entry) for entry in items ]
103
104  PH_PATTERN = re.compile('<ph[^>]*>([^<]*|[^<]*<ex>([^<]*)</ex>[^<]*)</ph>')
105
106  # Simplistic grit placeholder stripper.
107  @staticmethod
108  def _RemovePlaceholders(text):
109    result = ''
110    pos = 0
111    for m in PolicyDetails.PH_PATTERN.finditer(text):
112      result += text[pos:m.start(0)]
113      result += m.group(2) or m.group(1)
114      pos = m.end(0)
115    result += text[pos:]
116    return result
117
118
119def main():
120  parser = OptionParser(usage=__doc__)
121  parser.add_option('--pch', '--policy-constants-header', dest='header_path',
122                    help='generate header file of policy constants',
123                    metavar='FILE')
124  parser.add_option('--pcc', '--policy-constants-source', dest='source_path',
125                    help='generate source file of policy constants',
126                    metavar='FILE')
127  parser.add_option('--cpp', '--cloud-policy-protobuf',
128                    dest='cloud_policy_proto_path',
129                    help='generate cloud policy protobuf file',
130                    metavar='FILE')
131  parser.add_option('--csp', '--chrome-settings-protobuf',
132                    dest='chrome_settings_proto_path',
133                    help='generate chrome settings protobuf file',
134                    metavar='FILE')
135  parser.add_option('--cpd', '--cloud-policy-decoder',
136                    dest='cloud_policy_decoder_path',
137                    help='generate C++ code decoding the cloud policy protobuf',
138                    metavar='FILE')
139
140  (opts, args) = parser.parse_args()
141
142  if len(args) != 3:
143    print 'exactly platform, chromium_os flag and input file must be specified.'
144    parser.print_help()
145    return 2
146
147  os = args[0]
148  is_chromium_os = args[1] == '1'
149  template_file_name = args[2]
150
151  template_file_contents = _LoadJSONFile(template_file_name)
152  policy_details = [ PolicyDetails(policy, os, is_chromium_os)
153                     for policy in _Flatten(template_file_contents) ]
154  sorted_policy_details = sorted(policy_details, key=lambda policy: policy.name)
155
156  def GenerateFile(path, writer, sorted=False):
157    if path:
158      with open(path, 'w') as f:
159        _OutputGeneratedWarningHeader(f, template_file_name)
160        writer(sorted and sorted_policy_details or policy_details, os, f)
161
162  GenerateFile(opts.header_path, _WritePolicyConstantHeader, sorted=True)
163  GenerateFile(opts.source_path, _WritePolicyConstantSource, sorted=True)
164  GenerateFile(opts.cloud_policy_proto_path, _WriteCloudPolicyProtobuf)
165  GenerateFile(opts.chrome_settings_proto_path, _WriteChromeSettingsProtobuf)
166  GenerateFile(opts.cloud_policy_decoder_path, _WriteCloudPolicyDecoder)
167
168  return 0
169
170
171#------------------ shared helpers ---------------------------------#
172
173def _OutputGeneratedWarningHeader(f, template_file_path):
174  f.write('//\n'
175          '// DO NOT MODIFY THIS FILE DIRECTLY!\n'
176          '// IT IS GENERATED BY generate_policy_source.py\n'
177          '// FROM ' + template_file_path + '\n'
178          '//\n\n')
179
180
181COMMENT_WRAPPER = textwrap.TextWrapper()
182COMMENT_WRAPPER.width = 80
183COMMENT_WRAPPER.initial_indent = '// '
184COMMENT_WRAPPER.subsequent_indent = '// '
185COMMENT_WRAPPER.replace_whitespace = False
186
187
188# Writes a comment, each line prefixed by // and wrapped to 80 spaces.
189def _OutputComment(f, comment):
190  for line in comment.splitlines():
191    if len(line) == 0:
192      f.write('//')
193    else:
194      f.write(COMMENT_WRAPPER.fill(line))
195    f.write('\n')
196
197
198# Returns an iterator over all the policies in |template_file_contents|.
199def _Flatten(template_file_contents):
200  for policy in template_file_contents['policy_definitions']:
201    if policy['type'] == 'group':
202      for sub_policy in policy['policies']:
203        yield sub_policy
204    else:
205      yield policy
206
207
208def _LoadJSONFile(json_file):
209  with open(json_file, 'r') as f:
210    text = f.read()
211  return eval(text)
212
213
214#------------------ policy constants header ------------------------#
215
216def _WritePolicyConstantHeader(policies, os, f):
217  f.write('#ifndef CHROME_COMMON_POLICY_CONSTANTS_H_\n'
218          '#define CHROME_COMMON_POLICY_CONSTANTS_H_\n'
219          '\n'
220          '#include <string>\n'
221          '\n'
222          '#include "base/basictypes.h"\n'
223          '#include "base/values.h"\n'
224          '#include "components/policy/core/common/policy_details.h"\n'
225          '#include "components/policy/core/common/policy_map.h"\n'
226          '\n'
227          'namespace policy {\n'
228          '\n'
229          'namespace internal {\n'
230          'struct SchemaData;\n'
231          '}\n\n')
232
233  if os == 'win':
234    f.write('// The windows registry path where Chrome policy '
235            'configuration resides.\n'
236            'extern const wchar_t kRegistryChromePolicyKey[];\n')
237
238  f.write('#if defined (OS_CHROMEOS)\n'
239          '// Sets default values for enterprise users.\n'
240          'void SetEnterpriseUsersDefaults(PolicyMap* policy_map);\n'
241          '#endif\n'
242          '\n'
243          '// Returns the PolicyDetails for |policy| if |policy| is a known\n'
244          '// Chrome policy, otherwise returns NULL.\n'
245          'const PolicyDetails* GetChromePolicyDetails('
246              'const std::string& policy);\n'
247          '\n'
248          '// Returns the schema data of the Chrome policy schema.\n'
249          'const internal::SchemaData* GetChromeSchemaData();\n'
250          '\n')
251  f.write('// Key names for the policy settings.\n'
252          'namespace key {\n\n')
253  for policy in policies:
254    # TODO(joaodasilva): Include only supported policies in
255    # configuration_policy_handler.cc and configuration_policy_handler_list.cc
256    # so that these names can be conditional on 'policy.is_supported'.
257    # http://crbug.com/223616
258    f.write('extern const char k' + policy.name + '[];\n')
259  f.write('\n}  // namespace key\n\n'
260          '}  // namespace policy\n\n'
261          '#endif  // CHROME_COMMON_POLICY_CONSTANTS_H_\n')
262
263
264#------------------ policy constants source ------------------------#
265
266# A mapping of the simple schema types to base::Value::Types.
267SIMPLE_SCHEMA_NAME_MAP = {
268  'boolean': 'TYPE_BOOLEAN',
269  'integer': 'TYPE_INTEGER',
270  'null'   : 'TYPE_NULL',
271  'number' : 'TYPE_DOUBLE',
272  'string' : 'TYPE_STRING',
273}
274
275class SchemaNodesGenerator:
276  """Builds the internal structs to represent a JSON schema."""
277
278  def __init__(self, shared_strings):
279    """Creates a new generator.
280
281    |shared_strings| is a map of strings to a C expression that evaluates to
282    that string at runtime. This mapping can be used to reuse existing string
283    constants."""
284    self.shared_strings = shared_strings
285    self.schema_nodes = []
286    self.property_nodes = []
287    self.properties_nodes = []
288    self.restriction_nodes = []
289    self.int_enums = []
290    self.string_enums = []
291    self.simple_types = {
292      'boolean': None,
293      'integer': None,
294      'null': None,
295      'number': None,
296      'string': None,
297    }
298    self.stringlist_type = None
299    self.ranges = {}
300    self.id_map = {}
301
302  def GetString(self, s):
303    if s in self.shared_strings:
304      return self.shared_strings[s]
305    # Generate JSON escaped string, which is slightly different from desired
306    # C/C++ escaped string. Known differences includes unicode escaping format.
307    return json.dumps(s)
308
309  def AppendSchema(self, type, extra, comment=''):
310    index = len(self.schema_nodes)
311    self.schema_nodes.append((type, extra, comment))
312    return index
313
314  def AppendRestriction(self, first, second):
315    r = (str(first), str(second))
316    if not r in self.ranges:
317      self.ranges[r] = len(self.restriction_nodes)
318      self.restriction_nodes.append(r)
319    return self.ranges[r]
320
321  def GetSimpleType(self, name):
322    if self.simple_types[name] == None:
323      self.simple_types[name] = self.AppendSchema(
324          SIMPLE_SCHEMA_NAME_MAP[name],
325          -1,
326          'simple type: ' + name)
327    return self.simple_types[name]
328
329  def GetStringList(self):
330    if self.stringlist_type == None:
331      self.stringlist_type = self.AppendSchema(
332          'TYPE_LIST',
333          self.GetSimpleType('string'),
334          'simple type: stringlist')
335    return self.stringlist_type
336
337  def SchemaHaveRestriction(self, schema):
338    return any(keyword in schema for keyword in
339        ['minimum', 'maximum', 'enum', 'pattern'])
340
341  def IsConsecutiveInterval(self, seq):
342    sortedSeq = sorted(seq)
343    return all(sortedSeq[i] + 1 == sortedSeq[i + 1]
344               for i in xrange(len(sortedSeq) - 1))
345
346  def GetEnumIntegerType(self, schema, name):
347    assert all(type(x) == int for x in schema['enum'])
348    possible_values = schema['enum']
349    if self.IsConsecutiveInterval(possible_values):
350      index = self.AppendRestriction(max(possible_values), min(possible_values))
351      return self.AppendSchema('TYPE_INTEGER', index,
352          'integer with enumeration restriction (use range instead): %s' % name)
353    offset_begin = len(self.int_enums)
354    self.int_enums += possible_values
355    offset_end = len(self.int_enums)
356    return self.AppendSchema('TYPE_INTEGER',
357        self.AppendRestriction(offset_begin, offset_end),
358        'integer with enumeration restriction: %s' % name)
359
360  def GetEnumStringType(self, schema, name):
361    assert all(type(x) == str for x in schema['enum'])
362    offset_begin = len(self.string_enums)
363    self.string_enums += schema['enum']
364    offset_end = len(self.string_enums)
365    return self.AppendSchema('TYPE_STRING',
366        self.AppendRestriction(offset_begin, offset_end),
367        'string with enumeration restriction: %s' % name)
368
369  def GetEnumType(self, schema, name):
370    if len(schema['enum']) == 0:
371      raise RuntimeError('Empty enumeration in %s' % name)
372    elif schema['type'] == 'integer':
373      return self.GetEnumIntegerType(schema, name)
374    elif schema['type'] == 'string':
375      return self.GetEnumStringType(schema, name)
376    else:
377      raise RuntimeError('Unknown enumeration type in %s' % name)
378
379  def GetPatternType(self, schema, name):
380    if schema['type'] != 'string':
381      raise RuntimeError('Unknown pattern type in %s' % name)
382    pattern = schema['pattern']
383    # Try to compile the pattern to validate it, note that the syntax used
384    # here might be slightly different from re2.
385    # TODO(binjin): Add a python wrapper of re2 and use it here.
386    re.compile(pattern)
387    index = len(self.string_enums);
388    self.string_enums.append(pattern);
389    return self.AppendSchema('TYPE_STRING',
390        self.AppendRestriction(index, index),
391        'string with pattern restriction: %s' % name);
392
393  def GetRangedType(self, schema, name):
394    if schema['type'] != 'integer':
395      raise RuntimeError('Unknown ranged type in %s' % name)
396    min_value_set, max_value_set = False, False
397    if 'minimum' in schema:
398      min_value = int(schema['minimum'])
399      min_value_set = True
400    if 'maximum' in schema:
401      max_value = int(schema['minimum'])
402      max_value_set = True
403    if min_value_set and max_value_set and min_value > max_value:
404      raise RuntimeError('Invalid ranged type in %s' % name)
405    index = self.AppendRestriction(
406        str(max_value) if max_value_set else 'INT_MAX',
407        str(min_value) if min_value_set else 'INT_MIN')
408    return self.AppendSchema('TYPE_INTEGER',
409        index,
410        'integer with ranged restriction: %s' % name)
411
412  def Generate(self, schema, name):
413    """Generates the structs for the given schema.
414
415    |schema|: a valid JSON schema in a dictionary.
416    |name|: the name of the current node, for the generated comments."""
417    if schema.has_key('$ref'):
418      if schema.has_key('id'):
419        raise RuntimeError("Schemas with a $ref can't have an id")
420      if not isinstance(schema['$ref'], types.StringTypes):
421        raise RuntimeError("$ref attribute must be a string")
422      return schema['$ref']
423    if schema['type'] in self.simple_types:
424      if not self.SchemaHaveRestriction(schema):
425        # Simple types use shared nodes.
426        return self.GetSimpleType(schema['type'])
427      elif 'enum' in schema:
428        return self.GetEnumType(schema, name)
429      elif 'pattern' in schema:
430        return self.GetPatternType(schema, name)
431      else:
432        return self.GetRangedType(schema, name)
433
434    if schema['type'] == 'array':
435      # Special case for lists of strings, which is a common policy type.
436      # The 'type' may be missing if the schema has a '$ref' attribute.
437      if schema['items'].get('type', '') == 'string':
438        return self.GetStringList()
439      return self.AppendSchema('TYPE_LIST',
440          self.GenerateAndCollectID(schema['items'], 'items of ' + name))
441    elif schema['type'] == 'object':
442      # Reserve an index first, so that dictionaries come before their
443      # properties. This makes sure that the root node is the first in the
444      # SchemaNodes array.
445      index = self.AppendSchema('TYPE_DICTIONARY', -1)
446
447      if 'additionalProperties' in schema:
448        additionalProperties = self.GenerateAndCollectID(
449            schema['additionalProperties'],
450            'additionalProperties of ' + name)
451      else:
452        additionalProperties = -1
453
454      # Properties must be sorted by name, for the binary search lookup.
455      # Note that |properties| must be evaluated immediately, so that all the
456      # recursive calls to Generate() append the necessary child nodes; if
457      # |properties| were a generator then this wouldn't work.
458      sorted_properties = sorted(schema.get('properties', {}).items())
459      properties = [
460          (self.GetString(key), self.GenerateAndCollectID(subschema, key))
461          for key, subschema in sorted_properties ]
462
463      pattern_properties = []
464      for pattern, subschema in schema.get('patternProperties', {}).items():
465        pattern_properties.append((self.GetString(pattern),
466            self.GenerateAndCollectID(subschema, pattern)));
467
468      begin = len(self.property_nodes)
469      self.property_nodes += properties
470      end = len(self.property_nodes)
471      self.property_nodes += pattern_properties
472      pattern_end = len(self.property_nodes)
473
474      if index == 0:
475        self.root_properties_begin = begin
476        self.root_properties_end = end
477
478      extra = len(self.properties_nodes)
479      self.properties_nodes.append((begin, end, pattern_end,
480          additionalProperties, name))
481
482      # Set the right data at |index| now.
483      self.schema_nodes[index] = ('TYPE_DICTIONARY', extra, name)
484      return index
485    else:
486      assert False
487
488  def GenerateAndCollectID(self, schema, name):
489    """A wrapper of Generate(), will take the return value, check and add 'id'
490    attribute to self.id_map. The wrapper needs to be used for every call to
491    Generate().
492    """
493    index = self.Generate(schema, name)
494    if not schema.has_key('id'):
495      return index
496    id_str = schema['id']
497    if self.id_map.has_key(id_str):
498      raise RuntimeError('Duplicated id: ' + id_str)
499    self.id_map[id_str] = index
500    return index
501
502  def Write(self, f):
503    """Writes the generated structs to the given file.
504
505    |f| an open file to write to."""
506    f.write('const internal::SchemaNode kSchemas[] = {\n'
507            '//  Type                          Extra\n')
508    for type, extra, comment in self.schema_nodes:
509      type += ','
510      f.write('  { base::Value::%-18s %3d },  // %s\n' % (type, extra, comment))
511    f.write('};\n\n')
512
513    if self.property_nodes:
514      f.write('const internal::PropertyNode kPropertyNodes[] = {\n'
515              '//  Property                                          #Schema\n')
516      for key, schema in self.property_nodes:
517        key += ','
518        f.write('  { %-50s %6d },\n' % (key, schema))
519      f.write('};\n\n')
520
521    if self.properties_nodes:
522      f.write('const internal::PropertiesNode kProperties[] = {\n'
523              '//  Begin    End  PatternEnd Additional Properties\n')
524      for node in self.properties_nodes:
525        f.write('  { %5d, %5d, %10d, %5d },  // %s\n' % node)
526      f.write('};\n\n')
527
528    if self.restriction_nodes:
529      f.write('const internal::RestrictionNode kRestrictionNodes[] = {\n')
530      f.write('//   FIRST, SECOND\n')
531      for first, second in self.restriction_nodes:
532        f.write('  {{ %-8s %4s}},\n' % (first + ',', second))
533      f.write('};\n\n')
534
535    if self.int_enums:
536      f.write('const int kIntegerEnumerations[] = {\n')
537      for possible_values in self.int_enums:
538        f.write('  %d,\n' % possible_values)
539      f.write('};\n\n')
540
541    if self.string_enums:
542      f.write('const char* kStringEnumerations[] = {\n')
543      for possible_values in self.string_enums:
544        f.write('  %s,\n' % self.GetString(possible_values))
545      f.write('};\n\n')
546
547    f.write('const internal::SchemaData kChromeSchemaData = {\n'
548            '  kSchemas,\n')
549    f.write('  kPropertyNodes,\n' if self.property_nodes else '  NULL,\n')
550    f.write('  kProperties,\n' if self.properties_nodes else '  NULL,\n')
551    f.write('  kRestrictionNodes,\n' if self.restriction_nodes else '  NULL,\n')
552    f.write('  kIntegerEnumerations,\n' if self.int_enums else '  NULL,\n')
553    f.write('  kStringEnumerations,\n' if self.string_enums else '  NULL,\n')
554    f.write('};\n\n')
555
556  def GetByID(self, id_str):
557    if not isinstance(id_str, types.StringTypes):
558      return id_str
559    if not self.id_map.has_key(id_str):
560      raise RuntimeError('Invalid $ref: ' + id_str)
561    return self.id_map[id_str]
562
563  def ResolveID(self, index, params):
564    return params[:index] + (self.GetByID(params[index]),) + params[index+1:]
565
566  def ResolveReferences(self):
567    """Resolve reference mapping, required to be called after Generate()
568
569    After calling Generate(), the type of indices used in schema structures
570    might be either int or string. An int type suggests that it's a resolved
571    index, but for string type it's unresolved. Resolving a reference is as
572    simple as looking up for corresponding ID in self.id_map, and replace the
573    old index with the mapped index.
574    """
575    self.schema_nodes = map(partial(self.ResolveID, 1), self.schema_nodes)
576    self.property_nodes = map(partial(self.ResolveID, 1), self.property_nodes)
577    self.properties_nodes = map(partial(self.ResolveID, 3),
578        self.properties_nodes)
579
580def _WritePolicyConstantSource(policies, os, f):
581  f.write('#include "policy/policy_constants.h"\n'
582          '\n'
583          '#include <algorithm>\n'
584          '#include <climits>\n'
585          '\n'
586          '#include "base/logging.h"\n'
587          '#include "components/policy/core/common/schema_internal.h"\n'
588          '\n'
589          'namespace policy {\n'
590          '\n'
591          'namespace {\n'
592          '\n')
593
594  # Generate the Chrome schema.
595  chrome_schema = {
596    'type': 'object',
597    'properties': {},
598  }
599  shared_strings = {}
600  for policy in policies:
601    shared_strings[policy.name] = "key::k%s" % policy.name
602    if policy.is_supported:
603      chrome_schema['properties'][policy.name] = policy.schema
604
605  # Note: this list must be kept in sync with the known property list of the
606  # Chrome schema, so that binary seaching in the PropertyNode array gets the
607  # right index on this array as well. See the implementation of
608  # GetChromePolicyDetails() below.
609  f.write('const PolicyDetails kChromePolicyDetails[] = {\n'
610          '//  is_deprecated  is_device_policy  id    max_external_data_size\n')
611  for policy in policies:
612    if policy.is_supported:
613      f.write('  { %-14s %-16s %3s, %24s },\n' % (
614                  'true,' if policy.is_deprecated else 'false,',
615                  'true,' if policy.is_device_only else 'false,',
616                  policy.id,
617                  policy.max_size))
618  f.write('};\n\n')
619
620  schema_generator = SchemaNodesGenerator(shared_strings)
621  schema_generator.GenerateAndCollectID(chrome_schema, 'root node')
622  schema_generator.ResolveReferences()
623  schema_generator.Write(f)
624
625  f.write('bool CompareKeys(const internal::PropertyNode& node,\n'
626          '                 const std::string& key) {\n'
627          '  return node.key < key;\n'
628          '}\n\n')
629
630  f.write('}  // namespace\n\n')
631
632  if os == 'win':
633    f.write('#if defined(GOOGLE_CHROME_BUILD)\n'
634            'const wchar_t kRegistryChromePolicyKey[] = '
635            'L"' + CHROME_POLICY_KEY + '";\n'
636            '#else\n'
637            'const wchar_t kRegistryChromePolicyKey[] = '
638            'L"' + CHROMIUM_POLICY_KEY + '";\n'
639            '#endif\n\n')
640
641  f.write('const internal::SchemaData* GetChromeSchemaData() {\n'
642          '  return &kChromeSchemaData;\n'
643          '}\n\n')
644
645  f.write('#if defined (OS_CHROMEOS)\n'
646          'void SetEnterpriseUsersDefaults(PolicyMap* policy_map) {\n')
647
648  for policy in policies:
649    if policy.has_enterprise_default:
650      if policy.policy_type == 'TYPE_BOOLEAN':
651        creation_expression = 'new base::FundamentalValue(%s)' %\
652                              ('true' if policy.enterprise_default else 'false')
653      elif policy.policy_type == 'TYPE_INTEGER':
654        creation_expression = 'new base::FundamentalValue(%s)' %\
655                              policy.enterprise_default
656      elif policy.policy_type == 'TYPE_STRING':
657        creation_expression = 'new base::StringValue("%s")' %\
658                              policy.enterprise_default
659      else:
660        raise RuntimeError('Type %s of policy %s is not supported at '
661                           'enterprise defaults' % (policy.policy_type,
662                                                    policy.name))
663      f.write('  if (!policy_map->Get(key::k%s)) {\n'
664              '    policy_map->Set(key::k%s,\n'
665              '                    POLICY_LEVEL_MANDATORY,\n'
666              '                    POLICY_SCOPE_USER,\n'
667              '                    %s,\n'
668              '                    NULL);\n'
669              '  }\n' % (policy.name, policy.name, creation_expression))
670
671  f.write('}\n'
672          '#endif\n\n')
673
674  f.write('const PolicyDetails* GetChromePolicyDetails('
675              'const std::string& policy) {\n'
676          '  // First index in kPropertyNodes of the Chrome policies.\n'
677          '  static const int begin_index = %s;\n'
678          '  // One-past-the-end of the Chrome policies in kPropertyNodes.\n'
679          '  static const int end_index = %s;\n' %
680          (schema_generator.root_properties_begin,
681           schema_generator.root_properties_end))
682  f.write('  const internal::PropertyNode* begin =\n'
683          '      kPropertyNodes + begin_index;\n'
684          '  const internal::PropertyNode* end = kPropertyNodes + end_index;\n'
685          '  const internal::PropertyNode* it =\n'
686          '      std::lower_bound(begin, end, policy, CompareKeys);\n'
687          '  if (it == end || it->key != policy)\n'
688          '    return NULL;\n'
689          '  // This relies on kPropertyNodes from begin_index to end_index\n'
690          '  // having exactly the same policies (and in the same order) as\n'
691          '  // kChromePolicyDetails, so that binary searching on the first\n'
692          '  // gets the same results as a binary search on the second would.\n'
693          '  // However, kPropertyNodes has the policy names and\n'
694          '  // kChromePolicyDetails doesn\'t, so we obtain the index into\n'
695          '  // the second array by searching the first to avoid duplicating\n'
696          '  // the policy name pointers.\n'
697          '  // Offsetting |it| from |begin| here obtains the index we\'re\n'
698          '  // looking for.\n'
699          '  size_t index = it - begin;\n'
700          '  CHECK_LT(index, arraysize(kChromePolicyDetails));\n'
701          '  return kChromePolicyDetails + index;\n'
702          '}\n\n')
703
704  f.write('namespace key {\n\n')
705  for policy in policies:
706    # TODO(joaodasilva): Include only supported policies in
707    # configuration_policy_handler.cc and configuration_policy_handler_list.cc
708    # so that these names can be conditional on 'policy.is_supported'.
709    # http://crbug.com/223616
710    f.write('const char k{name}[] = "{name}";\n'.format(name=policy.name))
711  f.write('\n}  // namespace key\n\n'
712          '}  // namespace policy\n')
713
714
715#------------------ policy protobufs --------------------------------#
716
717CHROME_SETTINGS_PROTO_HEAD = '''
718syntax = "proto2";
719
720option optimize_for = LITE_RUNTIME;
721
722package enterprise_management;
723
724// For StringList and PolicyOptions.
725import "cloud_policy.proto";
726
727'''
728
729
730CLOUD_POLICY_PROTO_HEAD = '''
731syntax = "proto2";
732
733option optimize_for = LITE_RUNTIME;
734
735package enterprise_management;
736
737message StringList {
738  repeated string entries = 1;
739}
740
741message PolicyOptions {
742  enum PolicyMode {
743    // The given settings are applied regardless of user choice.
744    MANDATORY = 0;
745    // The user may choose to override the given settings.
746    RECOMMENDED = 1;
747    // No policy value is present and the policy should be ignored.
748    UNSET = 2;
749  }
750  optional PolicyMode mode = 1 [default = MANDATORY];
751}
752
753message BooleanPolicyProto {
754  optional PolicyOptions policy_options = 1;
755  optional bool value = 2;
756}
757
758message IntegerPolicyProto {
759  optional PolicyOptions policy_options = 1;
760  optional int64 value = 2;
761}
762
763message StringPolicyProto {
764  optional PolicyOptions policy_options = 1;
765  optional string value = 2;
766}
767
768message StringListPolicyProto {
769  optional PolicyOptions policy_options = 1;
770  optional StringList value = 2;
771}
772
773'''
774
775
776# Field IDs [1..RESERVED_IDS] will not be used in the wrapping protobuf.
777RESERVED_IDS = 2
778
779
780def _WritePolicyProto(f, policy, fields):
781  _OutputComment(f, policy.caption + '\n\n' + policy.desc)
782  if policy.items is not None:
783    _OutputComment(f, '\nValid values:')
784    for item in policy.items:
785      _OutputComment(f, '  %s: %s' % (str(item.value), item.caption))
786  if policy.policy_type == 'TYPE_DICTIONARY':
787    _OutputComment(f, '\nValue schema:\n%s' %
788                   json.dumps(policy.schema, sort_keys=True, indent=4,
789                              separators=(',', ': ')))
790  _OutputComment(f, '\nSupported on: %s' % ', '.join(policy.platforms))
791  if policy.can_be_recommended and not policy.can_be_mandatory:
792    _OutputComment(f, '\nNote: this policy must have a RECOMMENDED ' +\
793                      'PolicyMode set in PolicyOptions.')
794  f.write('message %sProto {\n' % policy.name)
795  f.write('  optional PolicyOptions policy_options = 1;\n')
796  f.write('  optional %s %s = 2;\n' % (policy.protobuf_type, policy.name))
797  f.write('}\n\n')
798  fields += [ '  optional %sProto %s = %s;\n' %
799              (policy.name, policy.name, policy.id + RESERVED_IDS) ]
800
801
802def _WriteChromeSettingsProtobuf(policies, os, f):
803  f.write(CHROME_SETTINGS_PROTO_HEAD)
804
805  fields = []
806  f.write('// PBs for individual settings.\n\n')
807  for policy in policies:
808    # Note: this protobuf also gets the unsupported policies, since it's an
809    # exaustive list of all the supported user policies on any platform.
810    if not policy.is_device_only:
811      _WritePolicyProto(f, policy, fields)
812
813  f.write('// --------------------------------------------------\n'
814          '// Big wrapper PB containing the above groups.\n\n'
815          'message ChromeSettingsProto {\n')
816  f.write(''.join(fields))
817  f.write('}\n\n')
818
819
820def _WriteCloudPolicyProtobuf(policies, os, f):
821  f.write(CLOUD_POLICY_PROTO_HEAD)
822  f.write('message CloudPolicySettings {\n')
823  for policy in policies:
824    if policy.is_supported and not policy.is_device_only:
825      f.write('  optional %sPolicyProto %s = %s;\n' %
826              (policy.policy_protobuf_type, policy.name,
827               policy.id + RESERVED_IDS))
828  f.write('}\n\n')
829
830
831#------------------ protobuf decoder -------------------------------#
832
833CPP_HEAD = '''
834#include <limits>
835#include <string>
836
837#include "base/basictypes.h"
838#include "base/callback.h"
839#include "base/json/json_reader.h"
840#include "base/logging.h"
841#include "base/memory/scoped_ptr.h"
842#include "base/memory/weak_ptr.h"
843#include "base/values.h"
844#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
845#include "components/policy/core/common/external_data_fetcher.h"
846#include "components/policy/core/common/policy_map.h"
847#include "policy/policy_constants.h"
848#include "policy/proto/cloud_policy.pb.h"
849
850using google::protobuf::RepeatedPtrField;
851
852namespace policy {
853
854namespace em = enterprise_management;
855
856base::Value* DecodeIntegerValue(google::protobuf::int64 value) {
857  if (value < std::numeric_limits<int>::min() ||
858      value > std::numeric_limits<int>::max()) {
859    LOG(WARNING) << "Integer value " << value
860                 << " out of numeric limits, ignoring.";
861    return NULL;
862  }
863
864  return new base::FundamentalValue(static_cast<int>(value));
865}
866
867base::ListValue* DecodeStringList(const em::StringList& string_list) {
868  base::ListValue* list_value = new base::ListValue;
869  RepeatedPtrField<std::string>::const_iterator entry;
870  for (entry = string_list.entries().begin();
871       entry != string_list.entries().end(); ++entry) {
872    list_value->AppendString(*entry);
873  }
874  return list_value;
875}
876
877base::Value* DecodeJson(const std::string& json) {
878  scoped_ptr<base::Value> root(
879      base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS));
880
881  if (!root)
882    LOG(WARNING) << "Invalid JSON string, ignoring: " << json;
883
884  // Accept any Value type that parsed as JSON, and leave it to the handler to
885  // convert and check the concrete type.
886  return root.release();
887}
888
889void DecodePolicy(const em::CloudPolicySettings& policy,
890                  base::WeakPtr<CloudExternalDataManager> external_data_manager,
891                  PolicyMap* map) {
892'''
893
894
895CPP_FOOT = '''}
896
897}  // namespace policy
898'''
899
900
901def _CreateValue(type, arg):
902  if type == 'TYPE_BOOLEAN':
903    return 'new base::FundamentalValue(%s)' % arg
904  elif type == 'TYPE_INTEGER':
905    return 'DecodeIntegerValue(%s)' % arg
906  elif type == 'TYPE_STRING':
907    return 'new base::StringValue(%s)' % arg
908  elif type == 'TYPE_LIST':
909    return 'DecodeStringList(%s)' % arg
910  elif type == 'TYPE_DICTIONARY' or type == 'TYPE_EXTERNAL':
911    return 'DecodeJson(%s)' % arg
912  else:
913    raise NotImplementedError('Unknown type %s' % type)
914
915
916def _CreateExternalDataFetcher(type, name):
917  if type == 'TYPE_EXTERNAL':
918    return 'new ExternalDataFetcher(external_data_manager, key::k%s)' % name
919  return 'NULL'
920
921
922def _WritePolicyCode(f, policy):
923  membername = policy.name.lower()
924  proto_type = '%sPolicyProto' % policy.policy_protobuf_type
925  f.write('  if (policy.has_%s()) {\n' % membername)
926  f.write('    const em::%s& policy_proto = policy.%s();\n' %
927          (proto_type, membername))
928  f.write('    if (policy_proto.has_value()) {\n')
929  f.write('      PolicyLevel level = POLICY_LEVEL_MANDATORY;\n'
930          '      bool do_set = true;\n'
931          '      if (policy_proto.has_policy_options()) {\n'
932          '        do_set = false;\n'
933          '        switch(policy_proto.policy_options().mode()) {\n'
934          '          case em::PolicyOptions::MANDATORY:\n'
935          '            do_set = true;\n'
936          '            level = POLICY_LEVEL_MANDATORY;\n'
937          '            break;\n'
938          '          case em::PolicyOptions::RECOMMENDED:\n'
939          '            do_set = true;\n'
940          '            level = POLICY_LEVEL_RECOMMENDED;\n'
941          '            break;\n'
942          '          case em::PolicyOptions::UNSET:\n'
943          '            break;\n'
944          '        }\n'
945          '      }\n'
946          '      if (do_set) {\n')
947  f.write('        base::Value* value = %s;\n' %
948          (_CreateValue(policy.policy_type, 'policy_proto.value()')))
949  # TODO(bartfab): |value| == NULL indicates that the policy value could not be
950  # parsed successfully. Surface such errors in the UI.
951  f.write('        if (value) {\n')
952  f.write('          ExternalDataFetcher* external_data_fetcher = %s;\n' %
953          _CreateExternalDataFetcher(policy.policy_type, policy.name))
954  f.write('          map->Set(key::k%s, level, POLICY_SCOPE_USER,\n' %
955          policy.name)
956  f.write('                   value, external_data_fetcher);\n'
957          '        }\n'
958          '      }\n'
959          '    }\n'
960          '  }\n')
961
962
963def _WriteCloudPolicyDecoder(policies, os, f):
964  f.write(CPP_HEAD)
965  for policy in policies:
966    if policy.is_supported and not policy.is_device_only:
967      _WritePolicyCode(f, policy)
968  f.write(CPP_FOOT)
969
970
971if __name__ == '__main__':
972  sys.exit(main())
973