JSON.lua
author Ryan C. Gordon <icculus@icculus.org>
Fri, 23 Jun 2017 17:28:03 -0400
changeset 58 1390348facc7
parent 0 d7ee4e2ed49d
permissions -rw-r--r--
Command line tool that decrypts an OPVault keychain and dumps it to stdout.

To compile: gcc -o opvault opvault.c cJSON.c -lcrypto

Usage: ./opvault </path/to/mykeychain.opvault> <password>

This is just a proof of concept; I'll be recycling this into proper OPVault
support in 1pass later and deleting this tool.

This uses OpenSSL's libcrypto for the math instead of all the homegrown
crypto this project is otherwise using. I'll probably migrate the rest in
this direction, too, since this wasn't as bad as I expected to use and
gets you all the package-manager mojo of automatic bug fixes and security
patches and shared code, etc.

cJSON parses JSON in C. That is from https://github.com/DaveGamble/cJSON

An example OPVault keychain from AgileBits is available here:

https://cache.agilebits.com/security-kb/
icculus@0
     1
-- -*- coding: utf-8 -*-
icculus@0
     2
--
icculus@0
     3
-- Simple JSON encoding and decoding in pure Lua.
icculus@0
     4
--
icculus@0
     5
-- Copyright 2010-2013 Jeffrey Friedl
icculus@0
     6
-- http://regex.info/blog/
icculus@0
     7
--
icculus@0
     8
-- Latest version: http://regex.info/blog/lua/json
icculus@0
     9
--
icculus@0
    10
-- This code is released under a Creative Commons CC-BY "Attribution" License:
icculus@0
    11
-- http://creativecommons.org/licenses/by/3.0/deed.en_US
icculus@0
    12
--
icculus@0
    13
-- It can be used for any purpose so long as the copyright notice and
icculus@0
    14
-- web-page links above are maintained. Enjoy.
icculus@0
    15
--
icculus@0
    16
local VERSION = 20131118.9  -- version history at end of file
icculus@0
    17
local OBJDEF = { VERSION = VERSION }
icculus@0
    18
icculus@0
    19
icculus@0
    20
--
icculus@0
    21
-- Simple JSON encoding and decoding in pure Lua.
icculus@0
    22
-- http://www.json.org/
icculus@0
    23
--
icculus@0
    24
--
icculus@0
    25
--   JSON = (loadfile "JSON.lua")() -- one-time load of the routines
icculus@0
    26
--
icculus@0
    27
--   local lua_value = JSON:decode(raw_json_text)
icculus@0
    28
--
icculus@0
    29
--   local raw_json_text    = JSON:encode(lua_table_or_value)
icculus@0
    30
--   local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
icculus@0
    31
--
icculus@0
    32
--
icculus@0
    33
-- DECODING
icculus@0
    34
--
icculus@0
    35
--   JSON = (loadfile "JSON.lua")() -- one-time load of the routines
icculus@0
    36
--
icculus@0
    37
--   local lua_value = JSON:decode(raw_json_text)
icculus@0
    38
--
icculus@0
    39
--   If the JSON text is for an object or an array, e.g.
icculus@0
    40
--     { "what": "books", "count": 3 }
icculus@0
    41
--   or
icculus@0
    42
--     [ "Larry", "Curly", "Moe" ]
icculus@0
    43
--
icculus@0
    44
--   the result is a Lua table, e.g.
icculus@0
    45
--     { what = "books", count = 3 }
icculus@0
    46
--   or
icculus@0
    47
--     { "Larry", "Curly", "Moe" }
icculus@0
    48
--
icculus@0
    49
--
icculus@0
    50
--   The encode and decode routines accept an optional second argument, "etc", which is not used
icculus@0
    51
--   during encoding or decoding, but upon error is passed along to error handlers. It can be of any
icculus@0
    52
--   type (including nil).
icculus@0
    53
--
icculus@0
    54
--   With most errors during decoding, this code calls
icculus@0
    55
--
icculus@0
    56
--      JSON:onDecodeError(message, text, location, etc)
icculus@0
    57
--
icculus@0
    58
--   with a message about the error, and if known, the JSON text being parsed and the byte count
icculus@0
    59
--   where the problem was discovered. You can replace the default JSON:onDecodeError() with your
icculus@0
    60
--   own function.
icculus@0
    61
--
icculus@0
    62
--   The default onDecodeError() merely augments the message with data about the text and the
icculus@0
    63
--   location if known (and if a second 'etc' argument had been provided to decode(), its value is
icculus@0
    64
--   tacked onto the message as well), and then calls JSON.assert(), which itself defaults to Lua's
icculus@0
    65
--   built-in assert(), and can also be overridden.
icculus@0
    66
--
icculus@0
    67
--   For example, in an Adobe Lightroom plugin, you might use something like
icculus@0
    68
--
icculus@0
    69
--          function JSON:onDecodeError(message, text, location, etc)
icculus@0
    70
--             LrErrors.throwUserError("Internal Error: invalid JSON data")
icculus@0
    71
--          end
icculus@0
    72
--
icculus@0
    73
--   or even just
icculus@0
    74
--
icculus@0
    75
--          function JSON.assert(message)
icculus@0
    76
--             LrErrors.throwUserError("Internal Error: " .. message)
icculus@0
    77
--          end
icculus@0
    78
--
icculus@0
    79
--   If JSON:decode() is passed a nil, this is called instead:
icculus@0
    80
--
icculus@0
    81
--      JSON:onDecodeOfNilError(message, nil, nil, etc)
icculus@0
    82
--
icculus@0
    83
--   and if JSON:decode() is passed HTML instead of JSON, this is called:
icculus@0
    84
--
icculus@0
    85
--      JSON:onDecodeOfHTMLError(message, text, nil, etc)
icculus@0
    86
--
icculus@0
    87
--   The use of the fourth 'etc' argument allows stronger coordination between decoding and error
icculus@0
    88
--   reporting, especially when you provide your own error-handling routines. Continuing with the
icculus@0
    89
--   the Adobe Lightroom plugin example:
icculus@0
    90
--
icculus@0
    91
--          function JSON:onDecodeError(message, text, location, etc)
icculus@0
    92
--             local note = "Internal Error: invalid JSON data"
icculus@0
    93
--             if type(etc) = 'table' and etc.photo then
icculus@0
    94
--                note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName')
icculus@0
    95
--             end
icculus@0
    96
--             LrErrors.throwUserError(note)
icculus@0
    97
--          end
icculus@0
    98
--
icculus@0
    99
--            :
icculus@0
   100
--            :
icculus@0
   101
--
icculus@0
   102
--          for i, photo in ipairs(photosToProcess) do
icculus@0
   103
--               :             
icculus@0
   104
--               :             
icculus@0
   105
--               local data = JSON:decode(someJsonText, { photo = photo })
icculus@0
   106
--               :             
icculus@0
   107
--               :             
icculus@0
   108
--          end
icculus@0
   109
--
icculus@0
   110
--
icculus@0
   111
--
icculus@0
   112
--
icculus@0
   113
icculus@0
   114
-- DECODING AND STRICT TYPES
icculus@0
   115
--
icculus@0
   116
--   Because both JSON objects and JSON arrays are converted to Lua tables, it's not normally
icculus@0
   117
--   possible to tell which a JSON type a particular Lua table was derived from, or guarantee
icculus@0
   118
--   decode-encode round-trip equivalency.
icculus@0
   119
--
icculus@0
   120
--   However, if you enable strictTypes, e.g.
icculus@0
   121
--
icculus@0
   122
--      JSON = (loadfile "JSON.lua")() --load the routines
icculus@0
   123
--      JSON.strictTypes = true
icculus@0
   124
--
icculus@0
   125
--   then the Lua table resulting from the decoding of a JSON object or JSON array is marked via Lua
icculus@0
   126
--   metatable, so that when re-encoded with JSON:encode() it ends up as the appropriate JSON type.
icculus@0
   127
--
icculus@0
   128
--   (This is not the default because other routines may not work well with tables that have a
icculus@0
   129
--   metatable set, for example, Lightroom API calls.)
icculus@0
   130
--
icculus@0
   131
--
icculus@0
   132
-- ENCODING
icculus@0
   133
--
icculus@0
   134
--   JSON = (loadfile "JSON.lua")() -- one-time load of the routines
icculus@0
   135
--
icculus@0
   136
--   local raw_json_text    = JSON:encode(lua_table_or_value)
icculus@0
   137
--   local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
icculus@0
   138
icculus@0
   139
--   On error during encoding, this code calls:
icculus@0
   140
--
icculus@0
   141
--    JSON:onEncodeError(message, etc)
icculus@0
   142
--
icculus@0
   143
--   which you can override in your local JSON object.
icculus@0
   144
--
icculus@0
   145
--   If the Lua table contains both string and numeric keys, it fits neither JSON's
icculus@0
   146
--   idea of an object, nor its idea of an array. To get around this, when any string
icculus@0
   147
--   key exists (or when non-positive numeric keys exist), numeric keys are converted
icculus@0
   148
--   to strings.
icculus@0
   149
--
icculus@0
   150
--   For example, 
icculus@0
   151
--     JSON:encode({ "one", "two", "three", SOMESTRING = "some string" }))
icculus@0
   152
--   produces the JSON object
icculus@0
   153
--     {"1":"one","2":"two","3":"three","SOMESTRING":"some string"}
icculus@0
   154
--
icculus@0
   155
--   To prohibit this conversion and instead make it an error condition, set
icculus@0
   156
--      JSON.noKeyConversion = true
icculus@0
   157
icculus@0
   158
icculus@0
   159
--
icculus@0
   160
-- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT
icculus@0
   161
--
icculus@0
   162
--    assert
icculus@0
   163
--    onDecodeError
icculus@0
   164
--    onDecodeOfNilError
icculus@0
   165
--    onDecodeOfHTMLError
icculus@0
   166
--    onEncodeError
icculus@0
   167
--
icculus@0
   168
--  If you want to create a separate Lua JSON object with its own error handlers,
icculus@0
   169
--  you can reload JSON.lua or use the :new() method.
icculus@0
   170
--
icculus@0
   171
---------------------------------------------------------------------------
icculus@0
   172
icculus@0
   173
icculus@0
   174
local author = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json), version " .. tostring(VERSION) .. " ]-"
icculus@0
   175
local isArray  = { __tostring = function() return "JSON array"  end }    isArray.__index  = isArray
icculus@0
   176
local isObject = { __tostring = function() return "JSON object" end }    isObject.__index = isObject
icculus@0
   177
icculus@0
   178
icculus@0
   179
function OBJDEF:newArray(tbl)
icculus@0
   180
   return setmetatable(tbl or {}, isArray)
icculus@0
   181
end
icculus@0
   182
icculus@0
   183
function OBJDEF:newObject(tbl)
icculus@0
   184
   return setmetatable(tbl or {}, isObject)
icculus@0
   185
end
icculus@0
   186
icculus@0
   187
local function unicode_codepoint_as_utf8(codepoint)
icculus@0
   188
   --
icculus@0
   189
   -- codepoint is a number
icculus@0
   190
   --
icculus@0
   191
   if codepoint <= 127 then
icculus@0
   192
      return string.char(codepoint)
icculus@0
   193
icculus@0
   194
   elseif codepoint <= 2047 then
icculus@0
   195
      --
icculus@0
   196
      -- 110yyyxx 10xxxxxx         <-- useful notation from http://en.wikipedia.org/wiki/Utf8
icculus@0
   197
      --
icculus@0
   198
      local highpart = math.floor(codepoint / 0x40)
icculus@0
   199
      local lowpart  = codepoint - (0x40 * highpart)
icculus@0
   200
      return string.char(0xC0 + highpart,
icculus@0
   201
                         0x80 + lowpart)
icculus@0
   202
icculus@0
   203
   elseif codepoint <= 65535 then
icculus@0
   204
      --
icculus@0
   205
      -- 1110yyyy 10yyyyxx 10xxxxxx
icculus@0
   206
      --
icculus@0
   207
      local highpart  = math.floor(codepoint / 0x1000)
icculus@0
   208
      local remainder = codepoint - 0x1000 * highpart
icculus@0
   209
      local midpart   = math.floor(remainder / 0x40)
icculus@0
   210
      local lowpart   = remainder - 0x40 * midpart
icculus@0
   211
icculus@0
   212
      highpart = 0xE0 + highpart
icculus@0
   213
      midpart  = 0x80 + midpart
icculus@0
   214
      lowpart  = 0x80 + lowpart
icculus@0
   215
icculus@0
   216
      --
icculus@0
   217
      -- Check for an invalid character (thanks Andy R. at Adobe).
icculus@0
   218
      -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070
icculus@0
   219
      --
icculus@0
   220
      if ( highpart == 0xE0 and midpart < 0xA0 ) or
icculus@0
   221
         ( highpart == 0xED and midpart > 0x9F ) or
icculus@0
   222
         ( highpart == 0xF0 and midpart < 0x90 ) or
icculus@0
   223
         ( highpart == 0xF4 and midpart > 0x8F )
icculus@0
   224
      then
icculus@0
   225
         return "?"
icculus@0
   226
      else
icculus@0
   227
         return string.char(highpart,
icculus@0
   228
                            midpart,
icculus@0
   229
                            lowpart)
icculus@0
   230
      end
icculus@0
   231
icculus@0
   232
   else
icculus@0
   233
      --
icculus@0
   234
      -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx
icculus@0
   235
      --
icculus@0
   236
      local highpart  = math.floor(codepoint / 0x40000)
icculus@0
   237
      local remainder = codepoint - 0x40000 * highpart
icculus@0
   238
      local midA      = math.floor(remainder / 0x1000)
icculus@0
   239
      remainder       = remainder - 0x1000 * midA
icculus@0
   240
      local midB      = math.floor(remainder / 0x40)
icculus@0
   241
      local lowpart   = remainder - 0x40 * midB
icculus@0
   242
icculus@0
   243
      return string.char(0xF0 + highpart,
icculus@0
   244
                         0x80 + midA,
icculus@0
   245
                         0x80 + midB,
icculus@0
   246
                         0x80 + lowpart)
icculus@0
   247
   end
icculus@0
   248
end
icculus@0
   249
icculus@0
   250
function OBJDEF:onDecodeError(message, text, location, etc)
icculus@0
   251
   if text then
icculus@0
   252
      if location then
icculus@0
   253
         message = string.format("%s at char %d of: %s", message, location, text)
icculus@0
   254
      else
icculus@0
   255
         message = string.format("%s: %s", message, text)
icculus@0
   256
      end
icculus@0
   257
   end
icculus@0
   258
   if etc ~= nil then
icculus@0
   259
      message = message .. " (" .. OBJDEF:encode(etc) .. ")"
icculus@0
   260
   end
icculus@0
   261
icculus@0
   262
   if self.assert then
icculus@0
   263
      self.assert(false, message)
icculus@0
   264
   else
icculus@0
   265
      assert(false, message)
icculus@0
   266
   end
icculus@0
   267
end
icculus@0
   268
icculus@0
   269
OBJDEF.onDecodeOfNilError  = OBJDEF.onDecodeError
icculus@0
   270
OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError
icculus@0
   271
icculus@0
   272
function OBJDEF:onEncodeError(message, etc)
icculus@0
   273
   if etc ~= nil then
icculus@0
   274
      message = message .. " (" .. OBJDEF:encode(etc) .. ")"
icculus@0
   275
   end
icculus@0
   276
icculus@0
   277
   if self.assert then
icculus@0
   278
      self.assert(false, message)
icculus@0
   279
   else
icculus@0
   280
      assert(false, message)
icculus@0
   281
   end
icculus@0
   282
end
icculus@0
   283
icculus@0
   284
local function grok_number(self, text, start, etc)
icculus@0
   285
   --
icculus@0
   286
   -- Grab the integer part
icculus@0
   287
   --
icculus@0
   288
   local integer_part = text:match('^-?[1-9]%d*', start)
icculus@0
   289
                     or text:match("^-?0",        start)
icculus@0
   290
icculus@0
   291
   if not integer_part then
icculus@0
   292
      self:onDecodeError("expected number", text, start, etc)
icculus@0
   293
   end
icculus@0
   294
icculus@0
   295
   local i = start + integer_part:len()
icculus@0
   296
icculus@0
   297
   --
icculus@0
   298
   -- Grab an optional decimal part
icculus@0
   299
   --
icculus@0
   300
   local decimal_part = text:match('^%.%d+', i) or ""
icculus@0
   301
icculus@0
   302
   i = i + decimal_part:len()
icculus@0
   303
icculus@0
   304
   --
icculus@0
   305
   -- Grab an optional exponential part
icculus@0
   306
   --
icculus@0
   307
   local exponent_part = text:match('^[eE][-+]?%d+', i) or ""
icculus@0
   308
icculus@0
   309
   i = i + exponent_part:len()
icculus@0
   310
icculus@0
   311
   local full_number_text = integer_part .. decimal_part .. exponent_part
icculus@0
   312
   local as_number = tonumber(full_number_text)
icculus@0
   313
icculus@0
   314
   if not as_number then
icculus@0
   315
      self:onDecodeError("bad number", text, start, etc)
icculus@0
   316
   end
icculus@0
   317
icculus@0
   318
   return as_number, i
icculus@0
   319
end
icculus@0
   320
icculus@0
   321
icculus@0
   322
local function grok_string(self, text, start, etc)
icculus@0
   323
icculus@0
   324
   if text:sub(start,start) ~= '"' then
icculus@0
   325
      self:onDecodeError("expected string's opening quote", text, start, etc)
icculus@0
   326
   end
icculus@0
   327
icculus@0
   328
   local i = start + 1 -- +1 to bypass the initial quote
icculus@0
   329
   local text_len = text:len()
icculus@0
   330
   local VALUE = ""
icculus@0
   331
   while i <= text_len do
icculus@0
   332
      local c = text:sub(i,i)
icculus@0
   333
      if c == '"' then
icculus@0
   334
         return VALUE, i + 1
icculus@0
   335
      end
icculus@0
   336
      if c ~= '\\' then
icculus@0
   337
         VALUE = VALUE .. c
icculus@0
   338
         i = i + 1
icculus@0
   339
      elseif text:match('^\\b', i) then
icculus@0
   340
         VALUE = VALUE .. "\b"
icculus@0
   341
         i = i + 2
icculus@0
   342
      elseif text:match('^\\f', i) then
icculus@0
   343
         VALUE = VALUE .. "\f"
icculus@0
   344
         i = i + 2
icculus@0
   345
      elseif text:match('^\\n', i) then
icculus@0
   346
         VALUE = VALUE .. "\n"
icculus@0
   347
         i = i + 2
icculus@0
   348
      elseif text:match('^\\r', i) then
icculus@0
   349
         VALUE = VALUE .. "\r"
icculus@0
   350
         i = i + 2
icculus@0
   351
      elseif text:match('^\\t', i) then
icculus@0
   352
         VALUE = VALUE .. "\t"
icculus@0
   353
         i = i + 2
icculus@0
   354
      else
icculus@0
   355
         local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)
icculus@0
   356
         if hex then
icculus@0
   357
            i = i + 6 -- bypass what we just read
icculus@0
   358
icculus@0
   359
            -- We have a Unicode codepoint. It could be standalone, or if in the proper range and
icculus@0
   360
            -- followed by another in a specific range, it'll be a two-code surrogate pair.
icculus@0
   361
            local codepoint = tonumber(hex, 16)
icculus@0
   362
            if codepoint >= 0xD800 and codepoint <= 0xDBFF then
icculus@0
   363
               -- it's a hi surrogate... see whether we have a following low
icculus@0
   364
               local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)
icculus@0
   365
               if lo_surrogate then
icculus@0
   366
                  i = i + 6 -- bypass the low surrogate we just read
icculus@0
   367
                  codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16)
icculus@0
   368
               else
icculus@0
   369
                  -- not a proper low, so we'll just leave the first codepoint as is and spit it out.
icculus@0
   370
               end
icculus@0
   371
            end
icculus@0
   372
            VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint)
icculus@0
   373
icculus@0
   374
         else
icculus@0
   375
icculus@0
   376
            -- just pass through what's escaped
icculus@0
   377
            VALUE = VALUE .. text:match('^\\(.)', i)
icculus@0
   378
            i = i + 2
icculus@0
   379
         end
icculus@0
   380
      end
icculus@0
   381
   end
icculus@0
   382
icculus@0
   383
   self:onDecodeError("unclosed string", text, start, etc)
icculus@0
   384
end
icculus@0
   385
icculus@0
   386
local function skip_whitespace(text, start)
icculus@0
   387
icculus@0
   388
   local match_start, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2
icculus@0
   389
   if match_end then
icculus@0
   390
      return match_end + 1
icculus@0
   391
   else
icculus@0
   392
      return start
icculus@0
   393
   end
icculus@0
   394
end
icculus@0
   395
icculus@0
   396
local grok_one -- assigned later
icculus@0
   397
icculus@0
   398
local function grok_object(self, text, start, etc)
icculus@0
   399
   if not text:sub(start,start) == '{' then
icculus@0
   400
      self:onDecodeError("expected '{'", text, start, etc)
icculus@0
   401
   end
icculus@0
   402
icculus@0
   403
   local i = skip_whitespace(text, start + 1) -- +1 to skip the '{'
icculus@0
   404
icculus@0
   405
   local VALUE = self.strictTypes and self:newObject { } or { }
icculus@0
   406
icculus@0
   407
   if text:sub(i,i) == '}' then
icculus@0
   408
      return VALUE, i + 1
icculus@0
   409
   end
icculus@0
   410
   local text_len = text:len()
icculus@0
   411
   while i <= text_len do
icculus@0
   412
      local key, new_i = grok_string(self, text, i, etc)
icculus@0
   413
icculus@0
   414
      i = skip_whitespace(text, new_i)
icculus@0
   415
icculus@0
   416
      if text:sub(i, i) ~= ':' then
icculus@0
   417
         self:onDecodeError("expected colon", text, i, etc)
icculus@0
   418
      end
icculus@0
   419
icculus@0
   420
      i = skip_whitespace(text, i + 1)
icculus@0
   421
icculus@0
   422
      local val, new_i = grok_one(self, text, i)
icculus@0
   423
icculus@0
   424
      VALUE[key] = val
icculus@0
   425
icculus@0
   426
      --
icculus@0
   427
      -- Expect now either '}' to end things, or a ',' to allow us to continue.
icculus@0
   428
      --
icculus@0
   429
      i = skip_whitespace(text, new_i)
icculus@0
   430
icculus@0
   431
      local c = text:sub(i,i)
icculus@0
   432
icculus@0
   433
      if c == '}' then
icculus@0
   434
         return VALUE, i + 1
icculus@0
   435
      end
icculus@0
   436
icculus@0
   437
      if text:sub(i, i) ~= ',' then
icculus@0
   438
         self:onDecodeError("expected comma or '}'", text, i, etc)
icculus@0
   439
      end
icculus@0
   440
icculus@0
   441
      i = skip_whitespace(text, i + 1)
icculus@0
   442
   end
icculus@0
   443
icculus@0
   444
   self:onDecodeError("unclosed '{'", text, start, etc)
icculus@0
   445
end
icculus@0
   446
icculus@0
   447
local function grok_array(self, text, start, etc)
icculus@0
   448
   if not text:sub(start,start) == '[' then
icculus@0
   449
      self:onDecodeError("expected '['", text, start, etc)
icculus@0
   450
   end
icculus@0
   451
icculus@0
   452
   local i = skip_whitespace(text, start + 1) -- +1 to skip the '['
icculus@0
   453
   local VALUE = self.strictTypes and self:newArray { } or { }
icculus@0
   454
   if text:sub(i,i) == ']' then
icculus@0
   455
      return VALUE, i + 1
icculus@0
   456
   end
icculus@0
   457
icculus@0
   458
   local text_len = text:len()
icculus@0
   459
   while i <= text_len do
icculus@0
   460
      local val, new_i = grok_one(self, text, i)
icculus@0
   461
icculus@0
   462
      table.insert(VALUE, val)
icculus@0
   463
icculus@0
   464
      i = skip_whitespace(text, new_i)
icculus@0
   465
icculus@0
   466
      --
icculus@0
   467
      -- Expect now either ']' to end things, or a ',' to allow us to continue.
icculus@0
   468
      --
icculus@0
   469
      local c = text:sub(i,i)
icculus@0
   470
      if c == ']' then
icculus@0
   471
         return VALUE, i + 1
icculus@0
   472
      end
icculus@0
   473
      if text:sub(i, i) ~= ',' then
icculus@0
   474
         self:onDecodeError("expected comma or '['", text, i, etc)
icculus@0
   475
      end
icculus@0
   476
      i = skip_whitespace(text, i + 1)
icculus@0
   477
   end
icculus@0
   478
   self:onDecodeError("unclosed '['", text, start, etc)
icculus@0
   479
end
icculus@0
   480
icculus@0
   481
icculus@0
   482
grok_one = function(self, text, start, etc)
icculus@0
   483
   -- Skip any whitespace
icculus@0
   484
   start = skip_whitespace(text, start)
icculus@0
   485
icculus@0
   486
   if start > text:len() then
icculus@0
   487
      self:onDecodeError("unexpected end of string", text, nil, etc)
icculus@0
   488
   end
icculus@0
   489
icculus@0
   490
   if text:find('^"', start) then
icculus@0
   491
      return grok_string(self, text, start, etc)
icculus@0
   492
icculus@0
   493
   elseif text:find('^[-0123456789 ]', start) then
icculus@0
   494
      return grok_number(self, text, start, etc)
icculus@0
   495
icculus@0
   496
   elseif text:find('^%{', start) then
icculus@0
   497
      return grok_object(self, text, start, etc)
icculus@0
   498
icculus@0
   499
   elseif text:find('^%[', start) then
icculus@0
   500
      return grok_array(self, text, start, etc)
icculus@0
   501
icculus@0
   502
   elseif text:find('^true', start) then
icculus@0
   503
      return true, start + 4
icculus@0
   504
icculus@0
   505
   elseif text:find('^false', start) then
icculus@0
   506
      return false, start + 5
icculus@0
   507
icculus@0
   508
   elseif text:find('^null', start) then
icculus@0
   509
      return nil, start + 4
icculus@0
   510
icculus@0
   511
   else
icculus@0
   512
      self:onDecodeError("can't parse JSON", text, start, etc)
icculus@0
   513
   end
icculus@0
   514
end
icculus@0
   515
icculus@0
   516
function OBJDEF:decode(text, etc)
icculus@0
   517
   if type(self) ~= 'table' or self.__index ~= OBJDEF then
icculus@0
   518
      OBJDEF:onDecodeError("JSON:decode must be called in method format", nil, nil, etc)
icculus@0
   519
   end
icculus@0
   520
icculus@0
   521
   if text == nil then
icculus@0
   522
      self:onDecodeOfNilError(string.format("nil passed to JSON:decode()"), nil, nil, etc)
icculus@0
   523
   elseif type(text) ~= 'string' then
icculus@0
   524
      self:onDecodeError(string.format("expected string argument to JSON:decode(), got %s", type(text)), nil, nil, etc)
icculus@0
   525
   end
icculus@0
   526
icculus@0
   527
   if text:match('^%s*$') then
icculus@0
   528
      return nil
icculus@0
   529
   end
icculus@0
   530
icculus@0
   531
   if text:match('^%s*<') then
icculus@0
   532
      -- Can't be JSON... we'll assume it's HTML
icculus@0
   533
      self:onDecodeOfHTMLError(string.format("html passed to JSON:decode()"), text, nil, etc)
icculus@0
   534
   end
icculus@0
   535
icculus@0
   536
   --
icculus@0
   537
   -- Ensure that it's not UTF-32 or UTF-16.
icculus@0
   538
   -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3),
icculus@0
   539
   -- but this package can't handle them.
icculus@0
   540
   --
icculus@0
   541
   if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then
icculus@0
   542
      self:onDecodeError("JSON package groks only UTF-8, sorry", text, nil, etc)
icculus@0
   543
   end
icculus@0
   544
icculus@0
   545
   local success, value = pcall(grok_one, self, text, 1, etc)
icculus@0
   546
   if success then
icculus@0
   547
      return value
icculus@0
   548
   else
icculus@0
   549
      -- should never get here... JSON parse errors should have been caught earlier
icculus@0
   550
      assert(false, value)
icculus@0
   551
      return nil
icculus@0
   552
   end
icculus@0
   553
end
icculus@0
   554
icculus@0
   555
local function backslash_replacement_function(c)
icculus@0
   556
   if c == "\n" then
icculus@0
   557
      return "\\n"
icculus@0
   558
   elseif c == "\r" then
icculus@0
   559
      return "\\r"
icculus@0
   560
   elseif c == "\t" then
icculus@0
   561
      return "\\t"
icculus@0
   562
   elseif c == "\b" then
icculus@0
   563
      return "\\b"
icculus@0
   564
   elseif c == "\f" then
icculus@0
   565
      return "\\f"
icculus@0
   566
   elseif c == '"' then
icculus@0
   567
      return '\\"'
icculus@0
   568
   elseif c == '\\' then
icculus@0
   569
      return '\\\\'
icculus@0
   570
   else
icculus@0
   571
      return string.format("\\u%04x", c:byte())
icculus@0
   572
   end
icculus@0
   573
end
icculus@0
   574
icculus@0
   575
local chars_to_be_escaped_in_JSON_string
icculus@0
   576
   = '['
icculus@0
   577
   ..    '"'    -- class sub-pattern to match a double quote
icculus@0
   578
   ..    '%\\'  -- class sub-pattern to match a backslash
icculus@0
   579
   ..    '%z'   -- class sub-pattern to match a null
icculus@0
   580
   ..    '\001' .. '-' .. '\031' -- class sub-pattern to match control characters
icculus@0
   581
   .. ']'
icculus@0
   582
icculus@0
   583
local function json_string_literal(value)
icculus@0
   584
   local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function)
icculus@0
   585
   return '"' .. newval .. '"'
icculus@0
   586
end
icculus@0
   587
icculus@0
   588
local function object_or_array(self, T, etc)
icculus@0
   589
   --
icculus@0
   590
   -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON
icculus@0
   591
   -- object. If there are only numbers, it's a JSON array.
icculus@0
   592
   --
icculus@0
   593
   -- If we'll be converting to a JSON object, we'll want to sort the keys so that the
icculus@0
   594
   -- end result is deterministic.
icculus@0
   595
   --
icculus@0
   596
   local string_keys = { }
icculus@0
   597
   local number_keys = { }
icculus@0
   598
   local number_keys_must_be_strings = false
icculus@0
   599
   local maximum_number_key
icculus@0
   600
icculus@0
   601
   for key in pairs(T) do
icculus@0
   602
      if type(key) == 'string' then
icculus@0
   603
         table.insert(string_keys, key)
icculus@0
   604
      elseif type(key) == 'number' then
icculus@0
   605
         table.insert(number_keys, key)
icculus@0
   606
         if key <= 0 or key >= math.huge then
icculus@0
   607
            number_keys_must_be_strings = true
icculus@0
   608
         elseif not maximum_number_key or key > maximum_number_key then
icculus@0
   609
            maximum_number_key = key
icculus@0
   610
         end
icculus@0
   611
      else
icculus@0
   612
         self:onEncodeError("can't encode table with a key of type " .. type(key), etc)
icculus@0
   613
      end
icculus@0
   614
   end
icculus@0
   615
icculus@0
   616
   if #string_keys == 0 and not number_keys_must_be_strings then
icculus@0
   617
      --
icculus@0
   618
      -- An empty table, or a numeric-only array
icculus@0
   619
      --
icculus@0
   620
      if #number_keys > 0 then
icculus@0
   621
         return nil, maximum_number_key -- an array
icculus@0
   622
      elseif tostring(T) == "JSON array" then
icculus@0
   623
         return nil
icculus@0
   624
      elseif tostring(T) == "JSON object" then
icculus@0
   625
         return { }
icculus@0
   626
      else
icculus@0
   627
         -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects
icculus@0
   628
         return nil
icculus@0
   629
      end
icculus@0
   630
   end
icculus@0
   631
icculus@0
   632
   table.sort(string_keys)
icculus@0
   633
icculus@0
   634
   local map
icculus@0
   635
   if #number_keys > 0 then
icculus@0
   636
      --
icculus@0
   637
      -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array
icculus@0
   638
      -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object.
icculus@0
   639
      --
icculus@0
   640
icculus@0
   641
      if JSON.noKeyConversion then
icculus@0
   642
         self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc)
icculus@0
   643
      end
icculus@0
   644
icculus@0
   645
      --
icculus@0
   646
      -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings
icculus@0
   647
      --
icculus@0
   648
      map = { }
icculus@0
   649
      for key, val in pairs(T) do
icculus@0
   650
         map[key] = val
icculus@0
   651
      end
icculus@0
   652
icculus@0
   653
      table.sort(number_keys)
icculus@0
   654
icculus@0
   655
      --
icculus@0
   656
      -- Throw numeric keys in there as strings
icculus@0
   657
      --
icculus@0
   658
      for _, number_key in ipairs(number_keys) do
icculus@0
   659
         local string_key = tostring(number_key)
icculus@0
   660
         if map[string_key] == nil then
icculus@0
   661
            table.insert(string_keys , string_key)
icculus@0
   662
            map[string_key] = T[number_key]
icculus@0
   663
         else
icculus@0
   664
            self:onEncodeError("conflict converting table with mixed-type keys into a JSON object: key " .. number_key .. " exists both as a string and a number.", etc)
icculus@0
   665
         end
icculus@0
   666
      end
icculus@0
   667
   end
icculus@0
   668
icculus@0
   669
   return string_keys, nil, map
icculus@0
   670
end
icculus@0
   671
icculus@0
   672
--
icculus@0
   673
-- Encode
icculus@0
   674
--
icculus@0
   675
local encode_value -- must predeclare because it calls itself
icculus@0
   676
function encode_value(self, value, parents, etc, indent) -- non-nil indent means pretty-printing
icculus@0
   677
icculus@0
   678
   if value == nil then
icculus@0
   679
      return 'null'
icculus@0
   680
icculus@0
   681
   elseif type(value) == 'string' then
icculus@0
   682
      return json_string_literal(value)
icculus@0
   683
icculus@0
   684
   elseif type(value) == 'number' then
icculus@0
   685
      if value ~= value then
icculus@0
   686
         --
icculus@0
   687
         -- NaN (Not a Number).
icculus@0
   688
         -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option.
icculus@0
   689
         --
icculus@0
   690
         return "null"
icculus@0
   691
      elseif value >= math.huge then
icculus@0
   692
         --
icculus@0
   693
         -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should
icculus@0
   694
         -- really be a package option. Note: at least with some implementations, positive infinity
icculus@0
   695
         -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is.
icculus@0
   696
         -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">="
icculus@0
   697
         -- case first.
icculus@0
   698
         --
icculus@0
   699
         return "1e+9999"
icculus@0
   700
      elseif value <= -math.huge then
icculus@0
   701
         --
icculus@0
   702
         -- Negative infinity.
icculus@0
   703
         -- JSON has no INF, so we have to fudge the best we can. This should really be a package option.
icculus@0
   704
         --
icculus@0
   705
         return "-1e+9999"
icculus@0
   706
      else
icculus@0
   707
         return tostring(value)
icculus@0
   708
      end
icculus@0
   709
icculus@0
   710
   elseif type(value) == 'boolean' then
icculus@0
   711
      return tostring(value)
icculus@0
   712
icculus@0
   713
   elseif type(value) ~= 'table' then
icculus@0
   714
      self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc)
icculus@0
   715
icculus@0
   716
   else
icculus@0
   717
      --
icculus@0
   718
      -- A table to be converted to either a JSON object or array.
icculus@0
   719
      --
icculus@0
   720
      local T = value
icculus@0
   721
icculus@0
   722
      if parents[T] then
icculus@0
   723
         self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc)
icculus@0
   724
      else
icculus@0
   725
         parents[T] = true
icculus@0
   726
      end
icculus@0
   727
icculus@0
   728
      local result_value
icculus@0
   729
icculus@0
   730
      local object_keys, maximum_number_key, map = object_or_array(self, T, etc)
icculus@0
   731
      if maximum_number_key then
icculus@0
   732
         --
icculus@0
   733
         -- An array...
icculus@0
   734
         --
icculus@0
   735
         local ITEMS = { }
icculus@0
   736
         for i = 1, maximum_number_key do
icculus@0
   737
            table.insert(ITEMS, encode_value(self, T[i], parents, etc, indent))
icculus@0
   738
         end
icculus@0
   739
icculus@0
   740
         if indent then
icculus@0
   741
            result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]"
icculus@0
   742
         else
icculus@0
   743
            result_value = "[" .. table.concat(ITEMS, ",") .. "]"
icculus@0
   744
         end
icculus@0
   745
icculus@0
   746
      elseif object_keys then
icculus@0
   747
         --
icculus@0
   748
         -- An object
icculus@0
   749
         --
icculus@0
   750
         local TT = map or T
icculus@0
   751
icculus@0
   752
         if indent then
icculus@0
   753
icculus@0
   754
            local KEYS = { }
icculus@0
   755
            local max_key_length = 0
icculus@0
   756
            for _, key in ipairs(object_keys) do
icculus@0
   757
               local encoded = encode_value(self, tostring(key), parents, etc, "")
icculus@0
   758
               max_key_length = math.max(max_key_length, #encoded)
icculus@0
   759
               table.insert(KEYS, encoded)
icculus@0
   760
            end
icculus@0
   761
            local key_indent = indent .. "    "
icculus@0
   762
            local subtable_indent = indent .. string.rep(" ", max_key_length + 2 + 4)
icculus@0
   763
            local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s"
icculus@0
   764
icculus@0
   765
            local COMBINED_PARTS = { }
icculus@0
   766
            for i, key in ipairs(object_keys) do
icculus@0
   767
               local encoded_val = encode_value(self, TT[key], parents, etc, subtable_indent)
icculus@0
   768
               table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val))
icculus@0
   769
            end
icculus@0
   770
            result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}"
icculus@0
   771
icculus@0
   772
         else
icculus@0
   773
icculus@0
   774
            local PARTS = { }
icculus@0
   775
            for _, key in ipairs(object_keys) do
icculus@0
   776
               local encoded_val = encode_value(self, TT[key],       parents, etc, indent)
icculus@0
   777
               local encoded_key = encode_value(self, tostring(key), parents, etc, indent)
icculus@0
   778
               table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val))
icculus@0
   779
            end
icculus@0
   780
            result_value = "{" .. table.concat(PARTS, ",") .. "}"
icculus@0
   781
icculus@0
   782
         end
icculus@0
   783
      else
icculus@0
   784
         --
icculus@0
   785
         -- An empty array/object... we'll treat it as an array, though it should really be an option
icculus@0
   786
         --
icculus@0
   787
         result_value = "[]"
icculus@0
   788
      end
icculus@0
   789
icculus@0
   790
      parents[T] = false
icculus@0
   791
      return result_value
icculus@0
   792
   end
icculus@0
   793
end
icculus@0
   794
icculus@0
   795
icculus@0
   796
function OBJDEF:encode(value, etc)
icculus@0
   797
   if type(self) ~= 'table' or self.__index ~= OBJDEF then
icculus@0
   798
      OBJDEF:onEncodeError("JSON:encode must be called in method format", etc)
icculus@0
   799
   end
icculus@0
   800
   return encode_value(self, value, {}, etc, nil)
icculus@0
   801
end
icculus@0
   802
icculus@0
   803
function OBJDEF:encode_pretty(value, etc)
icculus@0
   804
   if type(self) ~= 'table' or self.__index ~= OBJDEF then
icculus@0
   805
      OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc)
icculus@0
   806
   end
icculus@0
   807
   return encode_value(self, value, {}, etc, "")
icculus@0
   808
end
icculus@0
   809
icculus@0
   810
function OBJDEF.__tostring()
icculus@0
   811
   return "JSON encode/decode package"
icculus@0
   812
end
icculus@0
   813
icculus@0
   814
OBJDEF.__index = OBJDEF
icculus@0
   815
icculus@0
   816
function OBJDEF:new(args)
icculus@0
   817
   local new = { }
icculus@0
   818
icculus@0
   819
   if args then
icculus@0
   820
      for key, val in pairs(args) do
icculus@0
   821
         new[key] = val
icculus@0
   822
      end
icculus@0
   823
   end
icculus@0
   824
icculus@0
   825
   return setmetatable(new, OBJDEF)
icculus@0
   826
end
icculus@0
   827
icculus@0
   828
return OBJDEF:new()
icculus@0
   829
icculus@0
   830
--
icculus@0
   831
-- Version history:
icculus@0
   832
--
icculus@0
   833
--   20131118.9    Update for Lua 5.3... it seems that tostring(2/1) produces "2.0" instead of "2",
icculus@0
   834
--                 and this caused some problems.
icculus@0
   835
--
icculus@0
   836
--   20131031.8    Unified the code for encode() and encode_pretty(); they had been stupidly separate,
icculus@0
   837
--                 and had of course diverged (encode_pretty didn't get the fixes that encode got, so
icculus@0
   838
--                 sometimes produced incorrect results; thanks to Mattie for the heads up).
icculus@0
   839
--
icculus@0
   840
--                 Handle encoding tables with non-positive numeric keys (unlikely, but possible).
icculus@0
   841
--
icculus@0
   842
--                 If a table has both numeric and string keys, or its numeric keys are inappropriate
icculus@0
   843
--                 (such as being non-positive or infinite), the numeric keys are turned into
icculus@0
   844
--                 string keys appropriate for a JSON object. So, as before,
icculus@0
   845
--                         JSON:encode({ "one", "two", "three" })
icculus@0
   846
--                 produces the array
icculus@0
   847
--                         ["one","two","three"]
icculus@0
   848
--                 but now something with mixed key types like
icculus@0
   849
--                         JSON:encode({ "one", "two", "three", SOMESTRING = "some string" }))
icculus@0
   850
--                 instead of throwing an error produces an object:
icculus@0
   851
--                         {"1":"one","2":"two","3":"three","SOMESTRING":"some string"}
icculus@0
   852
--
icculus@0
   853
--                 To maintain the prior throw-an-error semantics, set
icculus@0
   854
--                      JSON.noKeyConversion = true
icculus@0
   855
--                 
icculus@0
   856
--   20131004.7    Release under a Creative Commons CC-BY license, which I should have done from day one, sorry.
icculus@0
   857
--
icculus@0
   858
--   20130120.6    Comment update: added a link to the specific page on my blog where this code can
icculus@0
   859
--                 be found, so that folks who come across the code outside of my blog can find updates
icculus@0
   860
--                 more easily.
icculus@0
   861
--
icculus@0
   862
--   20111207.5    Added support for the 'etc' arguments, for better error reporting.
icculus@0
   863
--
icculus@0
   864
--   20110731.4    More feedback from David Kolf on how to make the tests for Nan/Infinity system independent.
icculus@0
   865
--
icculus@0
   866
--   20110730.3    Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules:
icculus@0
   867
--
icculus@0
   868
--                   * When encoding lua for JSON, Sparse numeric arrays are now handled by
icculus@0
   869
--                     spitting out full arrays, such that
icculus@0
   870
--                        JSON:encode({"one", "two", [10] = "ten"})
icculus@0
   871
--                     returns
icculus@0
   872
--                        ["one","two",null,null,null,null,null,null,null,"ten"]
icculus@0
   873
--
icculus@0
   874
--                     In 20100810.2 and earlier, only up to the first non-null value would have been retained.
icculus@0
   875
--
icculus@0
   876
--                   * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999".
icculus@0
   877
--                     Version 20100810.2 and earlier created invalid JSON in both cases.
icculus@0
   878
--
icculus@0
   879
--                   * Unicode surrogate pairs are now detected when decoding JSON.
icculus@0
   880
--
icculus@0
   881
--   20100810.2    added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding
icculus@0
   882
--
icculus@0
   883
--   20100731.1    initial public release
icculus@0
   884
--