-> 文档在这里:
+# Advantage
-* [CF是什么?](https://github.com/CandyMi/core_framework/wiki/home)
+ - [x] Asynchronous I/O - The `Network I/O` and `File I/O` have been transformed, and the internal operations are now fully asynchronous.
-* [CF使用到的技术栈?](https://github.com/CandyMi/core_framework/wiki/MAP)
+ - [x] Rich built-in libraries - Many complete built-in libraries are implemented to complete the development and coverage of all aspects of basic applications.
-## 第一次安装CF
+ - [x] Automated scheduling - The bottom layer will automatically schedule `coroutines`, `timers` and `I/O`, and you can also choose to control it manually.
-> [CF如何安装?](https://github.com/CandyMi/core_framework/wiki/install)
+ - [x] Security and Encryption - Provides nearly 60 kinds of hash/digest/hash/signature algorithms, and internally supports SSL Server/Client.
-> [CF如何运行?](https://github.com/CandyMi/core_framework/wiki/RUN)
+ - [x] Data Exchange Format - Do you need `JSON` / `PROTOBUF` / `XML` / `MSGPACK` / `BSON` ? Great, they are all provided!
-> [如何在容器内运行?](https://github.com/CandyMi/core_framework/wiki/Docker)
+ - [x] Multi-database driver - `MySQL` / `PGSQL` / `MSSQL` / `MongoDB` are all available, you can also use it as you like.
-## 第一次运行CF
+ - [x] Enforcement and standardization -The framework forces you to write code, and you can also pass it on to your close friends.
-> [CF如何运行?](https://github.com/CandyMi/core_framework/wiki/RUN)
+# Website
-## 第一次使用CF的问题
+ * [Home](https://cfadmin.cn/)(Only Chinese.)
-> [我有一些cf使用上的问题?](https://github.com/CandyMi/core_framework/wiki/QA)
+ * [Wiki](https://doc.cfadmin.cn/)(Only Chinese.)
-## 联系作者
+# Build And Run
-> [issues](https://github.com/CandyMi/core_framework/issues)
+
+Following the Snobol tradition,
+LPeg defines patterns as first-class objects.
+That is, patterns are regular Lua values
+(represented by userdata).
+The library offers several functions to create
+and compose patterns.
+With the use of metamethods,
+several of these functions are provided as infix or prefix
+operators.
+On the one hand,
+the result is usually much more verbose than the typical
+encoding of patterns using the so called
+regular expressions
+(which typically are not regular expressions in the formal sense).
+On the other hand,
+first-class patterns allow much better documentation
+(as it is easy to comment the code,
+to break complex definitions in smaller parts, etc.)
+and are extensible,
+as we can define new functions to create and compose patterns.
+
+
+
+For a quick glance of the library,
+the following table summarizes its basic operations
+for creating patterns:
+
Matches patt behind the current position,
+ consuming no input
+
+
+
As a very simple example,
+lpeg.R("09")^1 creates a pattern that
+matches a non-empty sequence of digits.
+As a not so simple example,
+-lpeg.P(1)
+(which can be written as lpeg.P(-1),
+or simply -1 for operations expecting a pattern)
+matches an empty string only if it cannot match a single character;
+so, it succeeds only at the end of the subject.
+
+
+
+LPeg also offers the re module,
+which implements patterns following a regular-expression style
+(e.g., [09]+).
+(This module is 260 lines of Lua code,
+and of course it uses LPeg to parse regular expressions and
+translate them to regular LPeg patterns.)
+
+The matching function.
+It attempts to match the given pattern against the subject string.
+If the match succeeds,
+returns the index in the subject of the first character after the match,
+or the captured values
+(if the pattern captured any value).
+
+
+
+An optional numeric argument init makes the match
+start at that position in the subject string.
+As usual in Lua libraries,
+a negative value counts from the end.
+
+
+
+Unlike typical pattern-matching functions,
+match works only in anchored mode;
+that is, it tries to match the pattern with a prefix of
+the given subject string (at position init),
+not with an arbitrary substring of the subject.
+So, if we want to find a pattern anywhere in a string,
+we must either write a loop in Lua or write a pattern that
+matches anywhere.
+This second approach is easy and quite efficient;
+see examples.
+
+
+
lpeg.type (value)
+
+If the given value is a pattern,
+returns the string "pattern".
+Otherwise returns nil.
+
+
+
lpeg.version ()
+
+Returns a string with the running version of LPeg.
+
+
+
lpeg.setmaxstack (max)
+
+Sets a limit for the size of the backtrack stack used by LPeg to
+track calls and choices.
+(The default limit is 400.)
+Most well-written patterns need little backtrack levels and
+therefore you seldom need to change this limit;
+before changing it you should try to rewrite your
+pattern to avoid the need for extra space.
+Nevertheless, a few useful patterns may overflow.
+Also, with recursive grammars,
+subjects with deep recursion may also need larger limits.
+
+The following operations build patterns.
+All operations that expect a pattern as an argument
+may receive also strings, tables, numbers, booleans, or functions,
+which are translated to patterns according to
+the rules of function lpeg.P.
+
+
+
+
+
lpeg.P (value)
+
+Converts the given value into a proper pattern,
+according to the following rules:
+
+
+
+
+If the argument is a pattern,
+it is returned unmodified.
+
+
+
+If the argument is a string,
+it is translated to a pattern that matches the string literally.
+
+
+
+If the argument is a non-negative number n,
+the result is a pattern that matches exactly n characters.
+
+
+
+If the argument is a negative number -n,
+the result is a pattern that
+succeeds only if the input string has less than n characters left:
+lpeg.P(-n)
+is equivalent to -lpeg.P(n)
+(see the unary minus operation).
+
+
+
+If the argument is a boolean,
+the result is a pattern that always succeeds or always fails
+(according to the boolean value),
+without consuming any input.
+
+
+
+If the argument is a table,
+it is interpreted as a grammar
+(see Grammars).
+
+
+
+If the argument is a function,
+returns a pattern equivalent to a
+match-time capture over the empty string.
+
+
+
+
+
+
lpeg.B(patt)
+
+Returns a pattern that
+matches only if the input string at the current position
+is preceded by patt.
+Pattern patt must match only strings
+with some fixed length,
+and it cannot contain captures.
+
+
+
+Like the and predicate,
+this pattern never consumes any input,
+independently of success or failure.
+
+
+
+
lpeg.R ({range})
+
+Returns a pattern that matches any single character
+belonging to one of the given ranges.
+Each range is a string xy of length 2,
+representing all characters with code
+between the codes of x and y
+(both inclusive).
+
+
+
+As an example, the pattern
+lpeg.R("09") matches any digit,
+and lpeg.R("az", "AZ") matches any ASCII letter.
+
+
+
+
lpeg.S (string)
+
+Returns a pattern that matches any single character that
+appears in the given string.
+(The S stands for Set.)
+
+
+
+As an example, the pattern
+lpeg.S("+-*/") matches any arithmetic operator.
+
+
+
+Note that, if s is a character
+(that is, a string of length 1),
+then lpeg.P(s) is equivalent to lpeg.S(s)
+which is equivalent to lpeg.R(s..s).
+Note also that both lpeg.S("") and lpeg.R()
+are patterns that always fail.
+
+
+
+
lpeg.V (v)
+
+This operation creates a non-terminal (a variable)
+for a grammar.
+The created non-terminal refers to the rule indexed by v
+in the enclosing grammar.
+(See Grammars for details.)
+
+
+
+
lpeg.locale ([table])
+
+Returns a table with patterns for matching some character classes
+according to the current locale.
+The table has fields named
+alnum,
+alpha,
+cntrl,
+digit,
+graph,
+lower,
+print,
+punct,
+space,
+upper, and
+xdigit,
+each one containing a correspondent pattern.
+Each pattern matches any single character that belongs to its class.
+
+
+
+If called with an argument table,
+then it creates those fields inside the given table and
+returns that table.
+
+
+
+
#patt
+
+Returns a pattern that
+matches only if the input string matches patt,
+but without consuming any input,
+independently of success or failure.
+(This pattern is called an and predicate
+and it is equivalent to
+&patt in the original PEG notation.)
+
+
+
+
+This pattern never produces any capture.
+
+
+
+
-patt
+
+Returns a pattern that
+matches only if the input string does not match patt.
+It does not consume any input,
+independently of success or failure.
+(This pattern is equivalent to
+!patt in the original PEG notation.)
+
+
+
+As an example, the pattern
+-lpeg.P(1) matches only the end of string.
+
+
+
+This pattern never produces any captures,
+because either patt fails
+or -patt fails.
+(A failing pattern never produces captures.)
+
+
+
+
patt1 + patt2
+
+Returns a pattern equivalent to an ordered choice
+of patt1 and patt2.
+(This is denoted by patt1 / patt2 in the original PEG notation,
+not to be confused with the / operation in LPeg.)
+It matches either patt1 or patt2,
+with no backtracking once one of them succeeds.
+The identity element for this operation is the pattern
+lpeg.P(false),
+which always fails.
+
+
+
+If both patt1 and patt2 are
+character sets,
+this operation is equivalent to set union.
+
+Returns a pattern equivalent to !patt2 patt1.
+This pattern asserts that the input does not match
+patt2 and then matches patt1.
+
+
+
+When successful,
+this pattern produces all captures from patt1.
+It never produces any capture from patt2
+(as either patt2 fails or
+patt1 - patt2 fails).
+
+
+
+If both patt1 and patt2 are
+character sets,
+this operation is equivalent to set difference.
+Note that -patt is equivalent to "" - patt
+(or 0 - patt).
+If patt is a character set,
+1 - patt is its complement.
+
+
+
+
patt1 * patt2
+
+Returns a pattern that matches patt1
+and then matches patt2,
+starting where patt1 finished.
+The identity element for this operation is the
+pattern lpeg.P(true),
+which always succeeds.
+
+
+
+(LPeg uses the * operator
+[instead of the more obvious ..]
+both because it has
+the right priority and because in formal languages it is
+common to use a dot for denoting concatenation.)
+
+
+
+
patt^n
+
+If n is nonnegative,
+this pattern is
+equivalent to pattn patt*:
+It matches n or more occurrences of patt.
+
+
+
+Otherwise, when n is negative,
+this pattern is equivalent to (patt?)-n:
+It matches at most |n|
+occurrences of patt.
+
+
+
+In particular, patt^0 is equivalent to patt*,
+patt^1 is equivalent to patt+,
+and patt^-1 is equivalent to patt?
+in the original PEG notation.
+
+
+
+In all cases,
+the resulting pattern is greedy with no backtracking
+(also called a possessive repetition).
+That is, it matches only the longest possible sequence
+of matches for patt.
+
+With the use of Lua variables,
+it is possible to define patterns incrementally,
+with each new pattern using previously defined ones.
+However, this technique does not allow the definition of
+recursive patterns.
+For recursive patterns,
+we need real grammars.
+
+
+
+LPeg represents grammars with tables,
+where each entry is a rule.
+
+
+
+The call lpeg.V(v)
+creates a pattern that represents the nonterminal
+(or variable) with index v in a grammar.
+Because the grammar still does not exist when
+this function is evaluated,
+the result is an open reference to the respective rule.
+
+
+
+A table is fixed when it is converted to a pattern
+(either by calling lpeg.P or by using it wherein a
+pattern is expected).
+Then every open reference created by lpeg.V(v)
+is corrected to refer to the rule indexed by v in the table.
+
+
+
+When a table is fixed,
+the result is a pattern that matches its initial rule.
+The entry with index 1 in the table defines its initial rule.
+If that entry is a string,
+it is assumed to be the name of the initial rule.
+Otherwise, LPeg assumes that the entry 1 itself is the initial rule.
+
+
+
+As an example,
+the following grammar matches strings of a's and b's that
+have the same number of a's and b's:
+
+
+equalcount = lpeg.P{
+ "S"; -- initial rule name
+ S = "a" * lpeg.V"B" + "b" * lpeg.V"A" + "",
+ A = "a" * lpeg.V"S" + "b" * lpeg.V"A" * lpeg.V"A",
+ B = "b" * lpeg.V"S" + "a" * lpeg.V"B" * lpeg.V"B",
+} * -1
+
+
+It is equivalent to the following grammar in standard PEG notation:
+
+
+ S <- 'a' B / 'b' A / ''
+ A <- 'a' S / 'b' A A
+ B <- 'b' S / 'a' B B
+
+A capture is a pattern that produces values
+(the so called semantic information)
+according to what it matches.
+LPeg offers several kinds of captures,
+which produces values based on matches and combine these values to
+produce new values.
+Each capture may produce zero or more values.
+
+
+
+The following table summarizes the basic captures:
+
the returns of function applied to the captures
+ of patt; the application is done at match time
+
+
+
+A capture pattern produces its values only when it succeeds.
+For instance,
+the pattern lpeg.C(lpeg.P"a"^-1)
+produces the empty string when there is no "a"
+(because the pattern "a"? succeeds),
+while the pattern lpeg.C("a")^-1
+does not produce any value when there is no "a"
+(because the pattern "a" fails).
+A pattern inside a loop or inside a recursive structure
+produces values for each match.
+
+
+
+Usually,
+LPeg does not specify when (and if) it evaluates its captures.
+(As an example,
+consider the pattern lpeg.P"a" / func / 0.
+Because the "division" by 0 instructs LPeg to throw away the
+results from the pattern,
+LPeg may or may not call func.)
+Therefore, captures should avoid side effects.
+Moreover,
+most captures cannot affect the way a pattern matches a subject.
+The only exception to this rule is the
+so-called match-time capture.
+When a match-time capture matches,
+it forces the immediate evaluation of all its nested captures
+and then calls its corresponding function,
+which defines whether the match succeeds and also
+what values are produced.
+
+
+
lpeg.C (patt)
+
+Creates a simple capture,
+which captures the substring of the subject that matches patt.
+The captured value is a string.
+If patt has other captures,
+their values are returned after this one.
+
+
+
+
lpeg.Carg (n)
+
+Creates an argument capture.
+This pattern matches the empty string and
+produces the value given as the nth extra
+argument given in the call to lpeg.match.
+
+
+
+
lpeg.Cb (name)
+
+Creates a back capture.
+This pattern matches the empty string and
+produces the values produced by the most recent
+group capture named name
+(where name can be any Lua value).
+
+
+
+Most recent means the last
+complete
+outermost
+group capture with the given name.
+A Complete capture means that the entire pattern
+corresponding to the capture has matched.
+An Outermost capture means that the capture is not inside
+another complete capture.
+
+
+
+In the same way that LPeg does not specify when it evaluates captures,
+it does not specify whether it reuses
+values previously produced by the group
+or re-evaluates them.
+
+
+
lpeg.Cc ([value, ...])
+
+Creates a constant capture.
+This pattern matches the empty string and
+produces all given values as its captured values.
+
+
+
+
lpeg.Cf (patt, func)
+
+Creates a fold capture.
+If patt produces a list of captures
+C1 C2 ... Cn,
+this capture will produce the value
+func(...func(func(C1, C2), C3)...,
+ Cn),
+that is, it will fold
+(or accumulate, or reduce)
+the captures from patt using function func.
+
+
+
+This capture assumes that patt should produce
+at least one capture with at least one value (of any type),
+which becomes the initial value of an accumulator.
+(If you need a specific initial value,
+you may prefix a constant capture to patt.)
+For each subsequent capture,
+LPeg calls func
+with this accumulator as the first argument and all values produced
+by the capture as extra arguments;
+the first result from this call
+becomes the new value for the accumulator.
+The final value of the accumulator becomes the captured value.
+
+
+
+As an example,
+the following pattern matches a list of numbers separated
+by commas and returns their addition:
+
+
+-- matches a numeral and captures its numerical value
+number = lpeg.R"09"^1 / tonumber
+
+-- matches a list of numbers, capturing their values
+list = number * ("," * number)^0
+
+-- auxiliary function to add two numbers
+function add (acc, newvalue) return acc + newvalue end
+
+-- folds the list of numbers adding them
+sum = lpeg.Cf(list, add)
+
+-- example of use
+print(sum:match("10,30,43")) --> 83
+
+
+
+
lpeg.Cg (patt [, name])
+
+Creates a group capture.
+It groups all values returned by patt
+into a single capture.
+The group may be anonymous (if no name is given)
+or named with the given name
+(which can be any non-nil Lua value).
+
+
+
+An anonymous group serves to join values from several captures into
+a single capture.
+A named group has a different behavior.
+In most situations, a named group returns no values at all.
+Its values are only relevant for a following
+back capture or when used
+inside a table capture.
+
+
+
+
lpeg.Cp ()
+
+Creates a position capture.
+It matches the empty string and
+captures the position in the subject where the match occurs.
+The captured value is a number.
+
+
+
+
lpeg.Cs (patt)
+
+Creates a substitution capture,
+which captures the substring of the subject that matches patt,
+with substitutions.
+For any capture inside patt with a value,
+the substring that matched the capture is replaced by the capture value
+(which should be a string).
+The final captured value is the string resulting from
+all replacements.
+
+
+
+
lpeg.Ct (patt)
+
+Creates a table capture.
+This capture returns a table with all values from all anonymous captures
+made by patt inside this table in successive integer keys,
+starting at 1.
+Moreover,
+for each named capture group created by patt,
+the first value of the group is put into the table
+with the group name as its key.
+The captured value is only the table.
+
+
+
+
patt / string
+
+Creates a string capture.
+It creates a capture string based on string.
+The captured value is a copy of string,
+except that the character % works as an escape character:
+any sequence in string of the form %n,
+with n between 1 and 9,
+stands for the match of the n-th capture in patt.
+The sequence %0 stands for the whole match.
+The sequence %% stands for a single %.
+
+
+
+
patt / number
+
+Creates a numbered capture.
+For a non-zero number,
+the captured value is the n-th value
+captured by patt.
+When number is zero,
+there are no captured values.
+
+
+
+
patt / table
+
+Creates a query capture.
+It indexes the given table using as key the first value captured by
+patt,
+or the whole match if patt produced no value.
+The value at that index is the final value of the capture.
+If the table does not have that key,
+there is no captured value.
+
+
+
+
patt / function
+
+Creates a function capture.
+It calls the given function passing all captures made by
+patt as arguments,
+or the whole match if patt made no capture.
+The values returned by the function
+are the final values of the capture.
+In particular,
+if function returns no value,
+there is no captured value.
+
+
+
+
lpeg.Cmt(patt, function)
+
+Creates a match-time capture.
+Unlike all other captures,
+this one is evaluated immediately when a match occurs
+(even if it is part of a larger pattern that fails later).
+It forces the immediate evaluation of all its nested captures
+and then calls function.
+
+
+
+The given function gets as arguments the entire subject,
+the current position (after the match of patt),
+plus any capture values produced by patt.
+
+
+
+The first value returned by function
+defines how the match happens.
+If the call returns a number,
+the match succeeds
+and the returned number becomes the new current position.
+(Assuming a subject s and current position i,
+the returned number must be in the range [i, len(s) + 1].)
+If the call returns true,
+the match succeeds without consuming any input.
+(So, to return true is equivalent to return i.)
+If the call returns false, nil, or no value,
+the match fails.
+
+
+
+Any extra values returned by the function become the
+values produced by the capture.
+
+This example shows a very simple but complete program
+that builds and uses a pattern:
+
+
+local lpeg = require "lpeg"
+
+-- matches a word followed by end-of-string
+p = lpeg.R"az"^1 * -1
+
+print(p:match("hello")) --> 6
+print(lpeg.match(p, "hello")) --> 6
+print(p:match("1 hello")) --> nil
+
+
+The pattern is simply a sequence of one or more lower-case letters
+followed by the end of string (-1).
+The program calls match both as a method
+and as a function.
+In both sucessful cases,
+the match returns
+the index of the first character after the match,
+which is the string length plus one.
+
+
+
+
Name-value lists
+
+This example parses a list of name-value pairs and returns a table
+with those pairs:
+
+
+lpeg.locale(lpeg) -- adds locale entries into 'lpeg' table
+
+local space = lpeg.space^0
+local name = lpeg.C(lpeg.alpha^1) * space
+local sep = lpeg.S(",;") * space
+local pair = lpeg.Cg(name * "=" * space * name) * sep^-1
+local list = lpeg.Cf(lpeg.Ct("") * pair^0, rawset)
+t = list:match("a=b, c = hi; next = pi") --> { a = "b", c = "hi", next = "pi" }
+
+
+Each pair has the format name = name followed by
+an optional separator (a comma or a semicolon).
+The pair pattern encloses the pair in a group pattern,
+so that the names become the values of a single capture.
+The list pattern then folds these captures.
+It starts with an empty table,
+created by a table capture matching an empty string;
+then for each capture (a pair of names) it applies rawset
+over the accumulator (the table) and the capture values (the pair of names).
+rawset returns the table itself,
+so the accumulator is always the table.
+
+
+
Splitting a string
+
+The following code builds a pattern that
+splits a string using a given pattern
+sep as a separator:
+
+
+function split (s, sep)
+ sep = lpeg.P(sep)
+ local elem = lpeg.C((1 - sep)^0)
+ local p = elem * (sep * elem)^0
+ return lpeg.match(p, s)
+end
+
+
+First the function ensures that sep is a proper pattern.
+The pattern elem is a repetition of zero of more
+arbitrary characters as long as there is not a match against
+the separator.
+It also captures its match.
+The pattern p matches a list of elements separated
+by sep.
+
+
+
+If the split results in too many values,
+it may overflow the maximum number of values
+that can be returned by a Lua function.
+In this case,
+we can collect these values in a table:
+
+
+function split (s, sep)
+ sep = lpeg.P(sep)
+ local elem = lpeg.C((1 - sep)^0)
+ local p = lpeg.Ct(elem * (sep * elem)^0) -- make a table capture
+ return lpeg.match(p, s)
+end
+
+
+
+
Searching for a pattern
+
+The primitive match works only in anchored mode.
+If we want to find a pattern anywhere in a string,
+we must write a pattern that matches anywhere.
+
+
+
+Because patterns are composable,
+we can write a function that,
+given any arbitrary pattern p,
+returns a new pattern that searches for p
+anywhere in a string.
+There are several ways to do the search.
+One way is like this:
+
+This grammar has a straight reading:
+it matches p or skips one character and tries again.
+
+
+
+If we want to know where the pattern is in the string
+(instead of knowing only that it is there somewhere),
+we can add position captures to the pattern:
+
+
+local I = lpeg.Cp()
+function anywhere (p)
+ return lpeg.P{ I * p * I + 1 * lpeg.V(1) }
+end
+
+print(anywhere("world"):match("hello world!")) -> 7 12
+
+
+
+Another option for the search is like this:
+
+
+local I = lpeg.Cp()
+function anywhere (p)
+ return (1 - lpeg.P(p))^0 * I * p * I
+end
+
+
+Again the pattern has a straight reading:
+it skips as many characters as possible while not matching p,
+and then matches p (plus appropriate captures).
+
+
+
+If we want to look for a pattern only at word boundaries,
+we can use the following transformer:
+
+Reading the first (and only) rule of the given grammar,
+we have that a balanced string is
+an open parenthesis,
+followed by zero or more repetitions of either
+a non-parenthesis character or
+a balanced string (lpeg.V(1)),
+followed by a closing parenthesis.
+
+
+
+
Global substitution
+
+The next example does a job somewhat similar to string.gsub.
+It receives a pattern and a replacement value,
+and substitutes the replacement value for all occurrences of the pattern
+in a given string:
+
+A field is either a quoted field
+(which may contain any character except an individual quote,
+which may be written as two quotes that are replaced by one)
+or an unquoted field
+(which cannot contain commas, newlines, or quotes).
+A record is a list of fields separated by commas,
+ending with a newline or the string end (-1).
+
+
+
+As it is,
+the previous pattern returns each field as a separated result.
+If we add a table capture in the definition of record,
+the pattern will return instead a single table
+containing all fields:
+
+It is not difficult to use LPeg to convert a string from
+UTF-8 encoding to Latin 1 (ISO 8859-1):
+
+
+
+-- convert a two-byte UTF-8 sequence to a Latin 1 character
+local function f2 (s)
+ local c1, c2 = string.byte(s, 1, 2)
+ return string.char(c1 * 64 + c2 - 12416)
+end
+
+local utf8 = lpeg.R("\0\127")
+ + lpeg.R("\194\195") * lpeg.R("\128\191") / f2
+
+local decode_pattern = lpeg.Cs(utf8^0) * -1
+
+
+In this code,
+the definition of UTF-8 is already restricted to the
+Latin 1 range (from 0 to 255).
+Any encoding outside this range (as well as any invalid encoding)
+will not match that pattern.
+
+
+
+As the definition of decode_pattern demands that
+the pattern matches the whole input (because of the -1 at its end),
+any invalid string will simply fail to match,
+without any useful information about the problem.
+We can improve this situation redefining decode_pattern
+as follows:
+
+
+local function er (_, i) error("invalid encoding at position " .. i) end
+
+local decode_pattern = lpeg.Cs(utf8^0) * (-1 + lpeg.P(er))
+
+
+Now, if the pattern utf8^0 stops
+before the end of the string,
+an appropriate error function is called.
+
+
+
+
UTF-8 and Unicode
+
+We can extend the previous patterns to handle all Unicode code points.
+Of course,
+we cannot translate them to Latin 1 or any other one-byte encoding.
+Instead, our translation results in a array with the code points
+represented as numbers.
+The full code is here:
+
+A long string in Lua starts with the pattern [=*[
+and ends at the first occurrence of ]=*] with
+exactly the same number of equal signs.
+If the opening brackets are followed by a newline,
+this newline is discarded
+(that is, it is not part of the string).
+
+
+
+To match a long string in Lua,
+the pattern must capture the first repetition of equal signs and then,
+whenever it finds a candidate for closing the string,
+check whether it has the same number of equal signs.
+
+
+
+equals = lpeg.P"="^0
+open = "[" * lpeg.Cg(equals, "init") * "[" * lpeg.P"\n"^-1
+close = "]" * lpeg.C(equals) * "]"
+closeeq = lpeg.Cmt(close * lpeg.Cb("init"), function (s, i, a, b) return a == b end)
+string = open * lpeg.C((lpeg.P(1) - closeeq)^0) * close / 1
+
+
+
+The open pattern matches [=*[,
+capturing the repetitions of equal signs in a group named init;
+it also discharges an optional newline, if present.
+The close pattern matches ]=*],
+also capturing the repetitions of equal signs.
+The closeeq pattern first matches close;
+then it uses a back capture to recover the capture made
+by the previous open,
+which is named init;
+finally it uses a match-time capture to check
+whether both captures are equal.
+The string pattern starts with an open,
+then it goes as far as possible until matching closeeq,
+and then matches the final close.
+The final numbered capture simply discards
+the capture made by close.
+
+
+
+
Arithmetic expressions
+
+This example is a complete parser and evaluator for simple
+arithmetic expressions.
+We write it in two styles.
+The first approach first builds a syntax tree and then
+traverses this tree to compute the expression value:
+
+
+-- Lexical Elements
+local Space = lpeg.S(" \n\t")^0
+local Number = lpeg.C(lpeg.P"-"^-1 * lpeg.R("09")^1) * Space
+local TermOp = lpeg.C(lpeg.S("+-")) * Space
+local FactorOp = lpeg.C(lpeg.S("*/")) * Space
+local Open = "(" * Space
+local Close = ")" * Space
+
+-- Grammar
+local Exp, Term, Factor = lpeg.V"Exp", lpeg.V"Term", lpeg.V"Factor"
+G = lpeg.P{ Exp,
+ Exp = lpeg.Ct(Term * (TermOp * Term)^0);
+ Term = lpeg.Ct(Factor * (FactorOp * Factor)^0);
+ Factor = Number + Open * Exp * Close;
+}
+
+G = Space * G * -1
+
+-- Evaluator
+function eval (x)
+ if type(x) == "string" then
+ return tonumber(x)
+ else
+ local op1 = eval(x[1])
+ for i = 2, #x, 2 do
+ local op = x[i]
+ local op2 = eval(x[i + 1])
+ if (op == "+") then op1 = op1 + op2
+ elseif (op == "-") then op1 = op1 - op2
+ elseif (op == "*") then op1 = op1 * op2
+ elseif (op == "/") then op1 = op1 / op2
+ end
+ end
+ return op1
+ end
+end
+
+-- Parser/Evaluator
+function evalExp (s)
+ local t = lpeg.match(G, s)
+ if not t then error("syntax error", 2) end
+ return eval(t)
+end
+
+-- small example
+print(evalExp"3 + 5*9 / (1+1) - 12") --> 13.5
+
+
+
+The second style computes the expression value on the fly,
+without building the syntax tree.
+The following grammar takes this approach.
+(It assumes the same lexical elements as before.)
+
+
+-- Auxiliary function
+function eval (v1, op, v2)
+ if (op == "+") then return v1 + v2
+ elseif (op == "-") then return v1 - v2
+ elseif (op == "*") then return v1 * v2
+ elseif (op == "/") then return v1 / v2
+ end
+end
+
+-- Grammar
+local V = lpeg.V
+G = lpeg.P{ "Exp",
+ Exp = lpeg.Cf(V"Term" * lpeg.Cg(TermOp * V"Term")^0, eval);
+ Term = lpeg.Cf(V"Factor" * lpeg.Cg(FactorOp * V"Factor")^0, eval);
+ Factor = Number / tonumber + Open * V"Exp" * Close;
+}
+
+-- small example
+print(lpeg.match(G, "3 + 5*9 / (1+1) - 12")) --> 13.5
+
+
+Note the use of the fold (accumulator) capture.
+To compute the value of an expression,
+the accumulator starts with the value of the first term,
+and then applies eval over
+the accumulator, the operator,
+and the new term for each repetition.
+
+Permission is hereby granted, free of charge,
+to any person obtaining a copy of this software and
+associated documentation files (the "Software"),
+to deal in the Software without restriction,
+including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software,
+and to permit persons to whom the Software is
+furnished to do so,
+subject to the following conditions:
+
+
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of the Software.
+
+
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+
+
+
+
+
+
+
+
diff --git a/luaclib/src/lpeg/lpprint.c b/luaclib/src/lpeg/lpprint.c
new file mode 100644
index 00000000..ce48672a
--- /dev/null
+++ b/luaclib/src/lpeg/lpprint.c
@@ -0,0 +1,244 @@
+/*
+** $Id: lpprint.c $
+** Copyright 2007, Lua.org & PUC-Rio (see 'lpeg.html' for license)
+*/
+
+#include
+
+#include
+
+
+#include "lptypes.h"
+#include "lpprint.h"
+#include "lpcode.h"
+
+
+#if defined(LPEG_DEBUG)
+
+/*
+** {======================================================
+** Printing patterns (for debugging)
+** =======================================================
+*/
+
+
+void printcharset (const byte *st) {
+ int i;
+ printf("[");
+ for (i = 0; i <= UCHAR_MAX; i++) {
+ int first = i;
+ while (testchar(st, i) && i <= UCHAR_MAX) i++;
+ if (i - 1 == first) /* unary range? */
+ printf("(%02x)", first);
+ else if (i - 1 > first) /* non-empty range? */
+ printf("(%02x-%02x)", first, i - 1);
+ }
+ printf("]");
+}
+
+
+static const char *capkind (int kind) {
+ const char *const modes[] = {
+ "close", "position", "constant", "backref",
+ "argument", "simple", "table", "function",
+ "query", "string", "num", "substitution", "fold",
+ "runtime", "group"};
+ return modes[kind];
+}
+
+
+static void printjmp (const Instruction *op, const Instruction *p) {
+ printf("-> %d", (int)(p + (p + 1)->offset - op));
+}
+
+
+void printinst (const Instruction *op, const Instruction *p) {
+ const char *const names[] = {
+ "any", "char", "set",
+ "testany", "testchar", "testset",
+ "span", "behind",
+ "ret", "end",
+ "choice", "jmp", "call", "open_call",
+ "commit", "partial_commit", "back_commit", "failtwice", "fail", "giveup",
+ "fullcapture", "opencapture", "closecapture", "closeruntime"
+ };
+ printf("%02ld: %s ", (long)(p - op), names[p->i.code]);
+ switch ((Opcode)p->i.code) {
+ case IChar: {
+ printf("'%c'", p->i.aux);
+ break;
+ }
+ case ITestChar: {
+ printf("'%c'", p->i.aux); printjmp(op, p);
+ break;
+ }
+ case IFullCapture: {
+ printf("%s (size = %d) (idx = %d)",
+ capkind(getkind(p)), getoff(p), p->i.key);
+ break;
+ }
+ case IOpenCapture: {
+ printf("%s (idx = %d)", capkind(getkind(p)), p->i.key);
+ break;
+ }
+ case ISet: {
+ printcharset((p+1)->buff);
+ break;
+ }
+ case ITestSet: {
+ printcharset((p+2)->buff); printjmp(op, p);
+ break;
+ }
+ case ISpan: {
+ printcharset((p+1)->buff);
+ break;
+ }
+ case IOpenCall: {
+ printf("-> %d", (p + 1)->offset);
+ break;
+ }
+ case IBehind: {
+ printf("%d", p->i.aux);
+ break;
+ }
+ case IJmp: case ICall: case ICommit: case IChoice:
+ case IPartialCommit: case IBackCommit: case ITestAny: {
+ printjmp(op, p);
+ break;
+ }
+ default: break;
+ }
+ printf("\n");
+}
+
+
+void printpatt (Instruction *p, int n) {
+ Instruction *op = p;
+ while (p < op + n) {
+ printinst(op, p);
+ p += sizei(p);
+ }
+}
+
+
+#if defined(LPEG_DEBUG)
+static void printcap (Capture *cap) {
+ printf("%s (idx: %d - size: %d) -> %p\n",
+ capkind(cap->kind), cap->idx, cap->siz, cap->s);
+}
+
+
+void printcaplist (Capture *cap, Capture *limit) {
+ printf(">======\n");
+ for (; cap->s && (limit == NULL || cap < limit); cap++)
+ printcap(cap);
+ printf("=======\n");
+}
+#endif
+
+/* }====================================================== */
+
+
+/*
+** {======================================================
+** Printing trees (for debugging)
+** =======================================================
+*/
+
+static const char *tagnames[] = {
+ "char", "set", "any",
+ "true", "false",
+ "rep",
+ "seq", "choice",
+ "not", "and",
+ "call", "opencall", "rule", "grammar",
+ "behind",
+ "capture", "run-time"
+};
+
+
+void printtree (TTree *tree, int ident) {
+ int i;
+ for (i = 0; i < ident; i++) printf(" ");
+ printf("%s", tagnames[tree->tag]);
+ switch (tree->tag) {
+ case TChar: {
+ int c = tree->u.n;
+ if (isprint(c))
+ printf(" '%c'\n", c);
+ else
+ printf(" (%02X)\n", c);
+ break;
+ }
+ case TSet: {
+ printcharset(treebuffer(tree));
+ printf("\n");
+ break;
+ }
+ case TOpenCall: case TCall: {
+ assert(sib2(tree)->tag == TRule);
+ printf(" key: %d (rule: %d)\n", tree->key, sib2(tree)->cap);
+ break;
+ }
+ case TBehind: {
+ printf(" %d\n", tree->u.n);
+ printtree(sib1(tree), ident + 2);
+ break;
+ }
+ case TCapture: {
+ printf(" kind: '%s' key: %d\n", capkind(tree->cap), tree->key);
+ printtree(sib1(tree), ident + 2);
+ break;
+ }
+ case TRule: {
+ printf(" n: %d key: %d\n", tree->cap, tree->key);
+ printtree(sib1(tree), ident + 2);
+ break; /* do not print next rule as a sibling */
+ }
+ case TGrammar: {
+ TTree *rule = sib1(tree);
+ printf(" %d\n", tree->u.n); /* number of rules */
+ for (i = 0; i < tree->u.n; i++) {
+ printtree(rule, ident + 2);
+ rule = sib2(rule);
+ }
+ assert(rule->tag == TTrue); /* sentinel */
+ break;
+ }
+ default: {
+ int sibs = numsiblings[tree->tag];
+ printf("\n");
+ if (sibs >= 1) {
+ printtree(sib1(tree), ident + 2);
+ if (sibs >= 2)
+ printtree(sib2(tree), ident + 2);
+ }
+ break;
+ }
+ }
+}
+
+
+void printktable (lua_State *L, int idx) {
+ int n, i;
+ lua_getuservalue(L, idx);
+ if (lua_isnil(L, -1)) /* no ktable? */
+ return;
+ n = lua_rawlen(L, -1);
+ printf("[");
+ for (i = 1; i <= n; i++) {
+ printf("%d = ", i);
+ lua_rawgeti(L, -1, i);
+ if (lua_isstring(L, -1))
+ printf("%s ", lua_tostring(L, -1));
+ else
+ printf("%s ", lua_typename(L, lua_type(L, -1)));
+ lua_pop(L, 1);
+ }
+ printf("]\n");
+ /* leave ktable at the stack */
+}
+
+/* }====================================================== */
+
+#endif
diff --git a/luaclib/src/lpeg/lpprint.h b/luaclib/src/lpeg/lpprint.h
new file mode 100644
index 00000000..15ef121d
--- /dev/null
+++ b/luaclib/src/lpeg/lpprint.h
@@ -0,0 +1,36 @@
+/*
+** $Id: lpprint.h $
+*/
+
+
+#if !defined(lpprint_h)
+#define lpprint_h
+
+
+#include "lptree.h"
+#include "lpvm.h"
+
+
+#if defined(LPEG_DEBUG)
+
+void printpatt (Instruction *p, int n);
+void printtree (TTree *tree, int ident);
+void printktable (lua_State *L, int idx);
+void printcharset (const byte *st);
+void printcaplist (Capture *cap, Capture *limit);
+void printinst (const Instruction *op, const Instruction *p);
+
+#else
+
+#define printktable(L,idx) \
+ luaL_error(L, "function only implemented in debug mode")
+#define printtree(tree,i) \
+ luaL_error(L, "function only implemented in debug mode")
+#define printpatt(p,n) \
+ luaL_error(L, "function only implemented in debug mode")
+
+#endif
+
+
+#endif
+
diff --git a/luaclib/src/lpeg/lptree.c b/luaclib/src/lpeg/lptree.c
new file mode 100644
index 00000000..7cd8c546
--- /dev/null
+++ b/luaclib/src/lpeg/lptree.c
@@ -0,0 +1,1301 @@
+/*
+** $Id: lptree.c $
+** Copyright 2013, Lua.org & PUC-Rio (see 'lpeg.html' for license)
+*/
+
+#include
+
+#include
+
+#include "lptypes.h"
+#include "lpcap.h"
+#include "lpcode.h"
+#include "lpprint.h"
+#include "lptree.h"
+
+
+/* number of siblings for each tree */
+const byte numsiblings[] = {
+ 0, 0, 0, /* char, set, any */
+ 0, 0, /* true, false */
+ 1, /* rep */
+ 2, 2, /* seq, choice */
+ 1, 1, /* not, and */
+ 0, 0, 2, 1, /* call, opencall, rule, grammar */
+ 1, /* behind */
+ 1, 1 /* capture, runtime capture */
+};
+
+
+static TTree *newgrammar (lua_State *L, int arg);
+
+
+/*
+** returns a reasonable name for value at index 'idx' on the stack
+*/
+static const char *val2str (lua_State *L, int idx) {
+ const char *k = lua_tostring(L, idx);
+ if (k != NULL)
+ return lua_pushfstring(L, "%s", k);
+ else
+ return lua_pushfstring(L, "(a %s)", luaL_typename(L, idx));
+}
+
+
+/*
+** Fix a TOpenCall into a TCall node, using table 'postable' to
+** translate a key to its rule address in the tree. Raises an
+** error if key does not exist.
+*/
+static void fixonecall (lua_State *L, int postable, TTree *g, TTree *t) {
+ int n;
+ lua_rawgeti(L, -1, t->key); /* get rule's name */
+ lua_gettable(L, postable); /* query name in position table */
+ n = lua_tonumber(L, -1); /* get (absolute) position */
+ lua_pop(L, 1); /* remove position */
+ if (n == 0) { /* no position? */
+ lua_rawgeti(L, -1, t->key); /* get rule's name again */
+ luaL_error(L, "rule '%s' undefined in given grammar", val2str(L, -1));
+ }
+ t->tag = TCall;
+ t->u.ps = n - (t - g); /* position relative to node */
+ assert(sib2(t)->tag == TRule);
+ sib2(t)->key = t->key; /* fix rule's key */
+}
+
+
+/*
+** Transform left associative constructions into right
+** associative ones, for sequence and choice; that is:
+** (t11 + t12) + t2 => t11 + (t12 + t2)
+** (t11 * t12) * t2 => t11 * (t12 * t2)
+** (that is, Op (Op t11 t12) t2 => Op t11 (Op t12 t2))
+*/
+static void correctassociativity (TTree *tree) {
+ TTree *t1 = sib1(tree);
+ assert(tree->tag == TChoice || tree->tag == TSeq);
+ while (t1->tag == tree->tag) {
+ int n1size = tree->u.ps - 1; /* t1 == Op t11 t12 */
+ int n11size = t1->u.ps - 1;
+ int n12size = n1size - n11size - 1;
+ memmove(sib1(tree), sib1(t1), n11size * sizeof(TTree)); /* move t11 */
+ tree->u.ps = n11size + 1;
+ sib2(tree)->tag = tree->tag;
+ sib2(tree)->u.ps = n12size + 1;
+ }
+}
+
+
+/*
+** Make final adjustments in a tree. Fix open calls in tree 't',
+** making them refer to their respective rules or raising appropriate
+** errors (if not inside a grammar). Correct associativity of associative
+** constructions (making them right associative). Assume that tree's
+** ktable is at the top of the stack (for error messages).
+*/
+static void finalfix (lua_State *L, int postable, TTree *g, TTree *t) {
+ tailcall:
+ switch (t->tag) {
+ case TGrammar: /* subgrammars were already fixed */
+ return;
+ case TOpenCall: {
+ if (g != NULL) /* inside a grammar? */
+ fixonecall(L, postable, g, t);
+ else { /* open call outside grammar */
+ lua_rawgeti(L, -1, t->key);
+ luaL_error(L, "rule '%s' used outside a grammar", val2str(L, -1));
+ }
+ break;
+ }
+ case TSeq: case TChoice:
+ correctassociativity(t);
+ break;
+ }
+ switch (numsiblings[t->tag]) {
+ case 1: /* finalfix(L, postable, g, sib1(t)); */
+ t = sib1(t); goto tailcall;
+ case 2:
+ finalfix(L, postable, g, sib1(t));
+ t = sib2(t); goto tailcall; /* finalfix(L, postable, g, sib2(t)); */
+ default: assert(numsiblings[t->tag] == 0); break;
+ }
+}
+
+
+
+/*
+** {===================================================================
+** KTable manipulation
+**
+** - The ktable of a pattern 'p' can be shared by other patterns that
+** contain 'p' and no other constants. Because of this sharing, we
+** should not add elements to a 'ktable' unless it was freshly created
+** for the new pattern.
+**
+** - The maximum index in a ktable is USHRT_MAX, because trees and
+** patterns use unsigned shorts to store those indices.
+** ====================================================================
+*/
+
+/*
+** Create a new 'ktable' to the pattern at the top of the stack.
+*/
+static void newktable (lua_State *L, int n) {
+ lua_createtable(L, n, 0); /* create a fresh table */
+ lua_setuservalue(L, -2); /* set it as 'ktable' for pattern */
+}
+
+
+/*
+** Add element 'idx' to 'ktable' of pattern at the top of the stack;
+** Return index of new element.
+** If new element is nil, does not add it to table (as it would be
+** useless) and returns 0, as ktable[0] is always nil.
+*/
+static int addtoktable (lua_State *L, int idx) {
+ if (lua_isnil(L, idx)) /* nil value? */
+ return 0;
+ else {
+ int n;
+ lua_getuservalue(L, -1); /* get ktable from pattern */
+ n = lua_rawlen(L, -1);
+ if (n >= USHRT_MAX)
+ luaL_error(L, "too many Lua values in pattern");
+ lua_pushvalue(L, idx); /* element to be added */
+ lua_rawseti(L, -2, ++n);
+ lua_pop(L, 1); /* remove 'ktable' */
+ return n;
+ }
+}
+
+
+/*
+** Return the number of elements in the ktable at 'idx'.
+** In Lua 5.2/5.3, default "environment" for patterns is nil, not
+** a table. Treat it as an empty table. In Lua 5.1, assumes that
+** the environment has no numeric indices (len == 0)
+*/
+static int ktablelen (lua_State *L, int idx) {
+ if (!lua_istable(L, idx)) return 0;
+ else return lua_rawlen(L, idx);
+}
+
+
+/*
+** Concatentate the contents of table 'idx1' into table 'idx2'.
+** (Assume that both indices are negative.)
+** Return the original length of table 'idx2' (or 0, if no
+** element was added, as there is no need to correct any index).
+*/
+static int concattable (lua_State *L, int idx1, int idx2) {
+ int i;
+ int n1 = ktablelen(L, idx1);
+ int n2 = ktablelen(L, idx2);
+ if (n1 + n2 > USHRT_MAX)
+ luaL_error(L, "too many Lua values in pattern");
+ if (n1 == 0) return 0; /* nothing to correct */
+ for (i = 1; i <= n1; i++) {
+ lua_rawgeti(L, idx1, i);
+ lua_rawseti(L, idx2 - 1, n2 + i); /* correct 'idx2' */
+ }
+ return n2;
+}
+
+
+/*
+** When joining 'ktables', constants from one of the subpatterns must
+** be renumbered; 'correctkeys' corrects their indices (adding 'n'
+** to each of them)
+*/
+static void correctkeys (TTree *tree, int n) {
+ if (n == 0) return; /* no correction? */
+ tailcall:
+ switch (tree->tag) {
+ case TOpenCall: case TCall: case TRunTime: case TRule: {
+ if (tree->key > 0)
+ tree->key += n;
+ break;
+ }
+ case TCapture: {
+ if (tree->key > 0 && tree->cap != Carg && tree->cap != Cnum)
+ tree->key += n;
+ break;
+ }
+ default: break;
+ }
+ switch (numsiblings[tree->tag]) {
+ case 1: /* correctkeys(sib1(tree), n); */
+ tree = sib1(tree); goto tailcall;
+ case 2:
+ correctkeys(sib1(tree), n);
+ tree = sib2(tree); goto tailcall; /* correctkeys(sib2(tree), n); */
+ default: assert(numsiblings[tree->tag] == 0); break;
+ }
+}
+
+
+/*
+** Join the ktables from p1 and p2 the ktable for the new pattern at the
+** top of the stack, reusing them when possible.
+*/
+static void joinktables (lua_State *L, int p1, TTree *t2, int p2) {
+ int n1, n2;
+ lua_getuservalue(L, p1); /* get ktables */
+ lua_getuservalue(L, p2);
+ n1 = ktablelen(L, -2);
+ n2 = ktablelen(L, -1);
+ if (n1 == 0 && n2 == 0) /* are both tables empty? */
+ lua_pop(L, 2); /* nothing to be done; pop tables */
+ else if (n2 == 0 || lp_equal(L, -2, -1)) { /* 2nd table empty or equal? */
+ lua_pop(L, 1); /* pop 2nd table */
+ lua_setuservalue(L, -2); /* set 1st ktable into new pattern */
+ }
+ else if (n1 == 0) { /* first table is empty? */
+ lua_setuservalue(L, -3); /* set 2nd table into new pattern */
+ lua_pop(L, 1); /* pop 1st table */
+ }
+ else {
+ lua_createtable(L, n1 + n2, 0); /* create ktable for new pattern */
+ /* stack: new p; ktable p1; ktable p2; new ktable */
+ concattable(L, -3, -1); /* from p1 into new ktable */
+ concattable(L, -2, -1); /* from p2 into new ktable */
+ lua_setuservalue(L, -4); /* new ktable becomes 'p' environment */
+ lua_pop(L, 2); /* pop other ktables */
+ correctkeys(t2, n1); /* correction for indices from p2 */
+ }
+}
+
+
+/*
+** copy 'ktable' of element 'idx' to new tree (on top of stack)
+*/
+static void copyktable (lua_State *L, int idx) {
+ lua_getuservalue(L, idx);
+ lua_setuservalue(L, -2);
+}
+
+
+/*
+** merge 'ktable' from 'stree' at stack index 'idx' into 'ktable'
+** from tree at the top of the stack, and correct corresponding
+** tree.
+*/
+static void mergektable (lua_State *L, int idx, TTree *stree) {
+ int n;
+ lua_getuservalue(L, -1); /* get ktables */
+ lua_getuservalue(L, idx);
+ n = concattable(L, -1, -2);
+ lua_pop(L, 2); /* remove both ktables */
+ correctkeys(stree, n);
+}
+
+
+/*
+** Create a new 'ktable' to the pattern at the top of the stack, adding
+** all elements from pattern 'p' (if not 0) plus element 'idx' to it.
+** Return index of new element.
+*/
+static int addtonewktable (lua_State *L, int p, int idx) {
+ newktable(L, 1);
+ if (p)
+ mergektable(L, p, NULL);
+ return addtoktable(L, idx);
+}
+
+/* }====================================================== */
+
+
+/*
+** {======================================================
+** Tree generation
+** =======================================================
+*/
+
+/*
+** In 5.2, could use 'luaL_testudata'...
+*/
+static int testpattern (lua_State *L, int idx) {
+ if (lua_touserdata(L, idx)) { /* value is a userdata? */
+ if (lua_getmetatable(L, idx)) { /* does it have a metatable? */
+ luaL_getmetatable(L, PATTERN_T);
+ if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */
+ lua_pop(L, 2); /* remove both metatables */
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+static Pattern *getpattern (lua_State *L, int idx) {
+ return (Pattern *)luaL_checkudata(L, idx, PATTERN_T);
+}
+
+
+static int getsize (lua_State *L, int idx) {
+ return (lua_rawlen(L, idx) - sizeof(Pattern)) / sizeof(TTree) + 1;
+}
+
+
+static TTree *gettree (lua_State *L, int idx, int *len) {
+ Pattern *p = getpattern(L, idx);
+ if (len)
+ *len = getsize(L, idx);
+ return p->tree;
+}
+
+
+/*
+** create a pattern. Set its uservalue (the 'ktable') equal to its
+** metatable. (It could be any empty sequence; the metatable is at
+** hand here, so we use it.)
+*/
+static TTree *newtree (lua_State *L, int len) {
+ size_t size = (len - 1) * sizeof(TTree) + sizeof(Pattern);
+ Pattern *p = (Pattern *)lua_newuserdata(L, size);
+ luaL_getmetatable(L, PATTERN_T);
+ lua_pushvalue(L, -1);
+ lua_setuservalue(L, -3);
+ lua_setmetatable(L, -2);
+ p->code = NULL; p->codesize = 0;
+ return p->tree;
+}
+
+
+static TTree *newleaf (lua_State *L, int tag) {
+ TTree *tree = newtree(L, 1);
+ tree->tag = tag;
+ return tree;
+}
+
+
+static TTree *newcharset (lua_State *L) {
+ TTree *tree = newtree(L, bytes2slots(CHARSETSIZE) + 1);
+ tree->tag = TSet;
+ loopset(i, treebuffer(tree)[i] = 0);
+ return tree;
+}
+
+
+/*
+** add to tree a sequence where first sibling is 'sib' (with size
+** 'sibsize'); returns position for second sibling
+*/
+static TTree *seqaux (TTree *tree, TTree *sib, int sibsize) {
+ tree->tag = TSeq; tree->u.ps = sibsize + 1;
+ memcpy(sib1(tree), sib, sibsize * sizeof(TTree));
+ return sib2(tree);
+}
+
+
+/*
+** Build a sequence of 'n' nodes, each with tag 'tag' and 'u.n' got
+** from the array 's' (or 0 if array is NULL). (TSeq is binary, so it
+** must build a sequence of sequence of sequence...)
+*/
+static void fillseq (TTree *tree, int tag, int n, const char *s) {
+ int i;
+ for (i = 0; i < n - 1; i++) { /* initial n-1 copies of Seq tag; Seq ... */
+ tree->tag = TSeq; tree->u.ps = 2;
+ sib1(tree)->tag = tag;
+ sib1(tree)->u.n = s ? (byte)s[i] : 0;
+ tree = sib2(tree);
+ }
+ tree->tag = tag; /* last one does not need TSeq */
+ tree->u.n = s ? (byte)s[i] : 0;
+}
+
+
+/*
+** Numbers as patterns:
+** 0 == true (always match); n == TAny repeated 'n' times;
+** -n == not (TAny repeated 'n' times)
+*/
+static TTree *numtree (lua_State *L, int n) {
+ if (n == 0)
+ return newleaf(L, TTrue);
+ else {
+ TTree *tree, *nd;
+ if (n > 0)
+ tree = nd = newtree(L, 2 * n - 1);
+ else { /* negative: code it as !(-n) */
+ n = -n;
+ tree = newtree(L, 2 * n);
+ tree->tag = TNot;
+ nd = sib1(tree);
+ }
+ fillseq(nd, TAny, n, NULL); /* sequence of 'n' any's */
+ return tree;
+ }
+}
+
+
+/*
+** Convert value at index 'idx' to a pattern
+*/
+static TTree *getpatt (lua_State *L, int idx, int *len) {
+ TTree *tree;
+ switch (lua_type(L, idx)) {
+ case LUA_TSTRING: {
+ size_t slen;
+ const char *s = lua_tolstring(L, idx, &slen); /* get string */
+ if (slen == 0) /* empty? */
+ tree = newleaf(L, TTrue); /* always match */
+ else {
+ tree = newtree(L, 2 * (slen - 1) + 1);
+ fillseq(tree, TChar, slen, s); /* sequence of 'slen' chars */
+ }
+ break;
+ }
+ case LUA_TNUMBER: {
+ int n = lua_tointeger(L, idx);
+ tree = numtree(L, n);
+ break;
+ }
+ case LUA_TBOOLEAN: {
+ tree = (lua_toboolean(L, idx) ? newleaf(L, TTrue) : newleaf(L, TFalse));
+ break;
+ }
+ case LUA_TTABLE: {
+ tree = newgrammar(L, idx);
+ break;
+ }
+ case LUA_TFUNCTION: {
+ tree = newtree(L, 2);
+ tree->tag = TRunTime;
+ tree->key = addtonewktable(L, 0, idx);
+ sib1(tree)->tag = TTrue;
+ break;
+ }
+ default: {
+ return gettree(L, idx, len);
+ }
+ }
+ lua_replace(L, idx); /* put new tree into 'idx' slot */
+ if (len)
+ *len = getsize(L, idx);
+ return tree;
+}
+
+
+/*
+** create a new tree, whith a new root and one sibling.
+** Sibling must be on the Lua stack, at index 1.
+*/
+static TTree *newroot1sib (lua_State *L, int tag) {
+ int s1;
+ TTree *tree1 = getpatt(L, 1, &s1);
+ TTree *tree = newtree(L, 1 + s1); /* create new tree */
+ tree->tag = tag;
+ memcpy(sib1(tree), tree1, s1 * sizeof(TTree));
+ copyktable(L, 1);
+ return tree;
+}
+
+
+/*
+** create a new tree, whith a new root and 2 siblings.
+** Siblings must be on the Lua stack, first one at index 1.
+*/
+static TTree *newroot2sib (lua_State *L, int tag) {
+ int s1, s2;
+ TTree *tree1 = getpatt(L, 1, &s1);
+ TTree *tree2 = getpatt(L, 2, &s2);
+ TTree *tree = newtree(L, 1 + s1 + s2); /* create new tree */
+ tree->tag = tag;
+ tree->u.ps = 1 + s1;
+ memcpy(sib1(tree), tree1, s1 * sizeof(TTree));
+ memcpy(sib2(tree), tree2, s2 * sizeof(TTree));
+ joinktables(L, 1, sib2(tree), 2);
+ return tree;
+}
+
+
+static int lp_P (lua_State *L) {
+ luaL_checkany(L, 1);
+ getpatt(L, 1, NULL);
+ lua_settop(L, 1);
+ return 1;
+}
+
+
+/*
+** sequence operator; optimizations:
+** false x => false, x true => x, true x => x
+** (cannot do x . false => false because x may have runtime captures)
+*/
+static int lp_seq (lua_State *L) {
+ TTree *tree1 = getpatt(L, 1, NULL);
+ TTree *tree2 = getpatt(L, 2, NULL);
+ if (tree1->tag == TFalse || tree2->tag == TTrue)
+ lua_pushvalue(L, 1); /* false . x == false, x . true = x */
+ else if (tree1->tag == TTrue)
+ lua_pushvalue(L, 2); /* true . x = x */
+ else
+ newroot2sib(L, TSeq);
+ return 1;
+}
+
+
+/*
+** choice operator; optimizations:
+** charset / charset => charset
+** true / x => true, x / false => x, false / x => x
+** (x / true is not equivalent to true)
+*/
+static int lp_choice (lua_State *L) {
+ Charset st1, st2;
+ TTree *t1 = getpatt(L, 1, NULL);
+ TTree *t2 = getpatt(L, 2, NULL);
+ if (tocharset(t1, &st1) && tocharset(t2, &st2)) {
+ TTree *t = newcharset(L);
+ loopset(i, treebuffer(t)[i] = st1.cs[i] | st2.cs[i]);
+ }
+ else if (nofail(t1) || t2->tag == TFalse)
+ lua_pushvalue(L, 1); /* true / x => true, x / false => x */
+ else if (t1->tag == TFalse)
+ lua_pushvalue(L, 2); /* false / x => x */
+ else
+ newroot2sib(L, TChoice);
+ return 1;
+}
+
+
+/*
+** p^n
+*/
+static int lp_star (lua_State *L) {
+ int size1;
+ int n = (int)luaL_checkinteger(L, 2);
+ TTree *tree1 = getpatt(L, 1, &size1);
+ if (n >= 0) { /* seq tree1 (seq tree1 ... (seq tree1 (rep tree1))) */
+ TTree *tree = newtree(L, (n + 1) * (size1 + 1));
+ if (nullable(tree1))
+ luaL_error(L, "loop body may accept empty string");
+ while (n--) /* repeat 'n' times */
+ tree = seqaux(tree, tree1, size1);
+ tree->tag = TRep;
+ memcpy(sib1(tree), tree1, size1 * sizeof(TTree));
+ }
+ else { /* choice (seq tree1 ... choice tree1 true ...) true */
+ TTree *tree;
+ n = -n;
+ /* size = (choice + seq + tree1 + true) * n, but the last has no seq */
+ tree = newtree(L, n * (size1 + 3) - 1);
+ for (; n > 1; n--) { /* repeat (n - 1) times */
+ tree->tag = TChoice; tree->u.ps = n * (size1 + 3) - 2;
+ sib2(tree)->tag = TTrue;
+ tree = sib1(tree);
+ tree = seqaux(tree, tree1, size1);
+ }
+ tree->tag = TChoice; tree->u.ps = size1 + 1;
+ sib2(tree)->tag = TTrue;
+ memcpy(sib1(tree), tree1, size1 * sizeof(TTree));
+ }
+ copyktable(L, 1);
+ return 1;
+}
+
+
+/*
+** #p == &p
+*/
+static int lp_and (lua_State *L) {
+ newroot1sib(L, TAnd);
+ return 1;
+}
+
+
+/*
+** -p == !p
+*/
+static int lp_not (lua_State *L) {
+ newroot1sib(L, TNot);
+ return 1;
+}
+
+
+/*
+** [t1 - t2] == Seq (Not t2) t1
+** If t1 and t2 are charsets, make their difference.
+*/
+static int lp_sub (lua_State *L) {
+ Charset st1, st2;
+ int s1, s2;
+ TTree *t1 = getpatt(L, 1, &s1);
+ TTree *t2 = getpatt(L, 2, &s2);
+ if (tocharset(t1, &st1) && tocharset(t2, &st2)) {
+ TTree *t = newcharset(L);
+ loopset(i, treebuffer(t)[i] = st1.cs[i] & ~st2.cs[i]);
+ }
+ else {
+ TTree *tree = newtree(L, 2 + s1 + s2);
+ tree->tag = TSeq; /* sequence of... */
+ tree->u.ps = 2 + s2;
+ sib1(tree)->tag = TNot; /* ...not... */
+ memcpy(sib1(sib1(tree)), t2, s2 * sizeof(TTree)); /* ...t2 */
+ memcpy(sib2(tree), t1, s1 * sizeof(TTree)); /* ... and t1 */
+ joinktables(L, 1, sib1(tree), 2);
+ }
+ return 1;
+}
+
+
+static int lp_set (lua_State *L) {
+ size_t l;
+ const char *s = luaL_checklstring(L, 1, &l);
+ TTree *tree = newcharset(L);
+ while (l--) {
+ setchar(treebuffer(tree), (byte)(*s));
+ s++;
+ }
+ return 1;
+}
+
+
+static int lp_range (lua_State *L) {
+ int arg;
+ int top = lua_gettop(L);
+ TTree *tree = newcharset(L);
+ for (arg = 1; arg <= top; arg++) {
+ int c;
+ size_t l;
+ const char *r = luaL_checklstring(L, arg, &l);
+ luaL_argcheck(L, l == 2, arg, "range must have two characters");
+ for (c = (byte)r[0]; c <= (byte)r[1]; c++)
+ setchar(treebuffer(tree), c);
+ }
+ return 1;
+}
+
+
+/*
+** Look-behind predicate
+*/
+static int lp_behind (lua_State *L) {
+ TTree *tree;
+ TTree *tree1 = getpatt(L, 1, NULL);
+ int n = fixedlen(tree1);
+ luaL_argcheck(L, n >= 0, 1, "pattern may not have fixed length");
+ luaL_argcheck(L, !hascaptures(tree1), 1, "pattern have captures");
+ luaL_argcheck(L, n <= MAXBEHIND, 1, "pattern too long to look behind");
+ tree = newroot1sib(L, TBehind);
+ tree->u.n = n;
+ return 1;
+}
+
+
+/*
+** Create a non-terminal
+*/
+static int lp_V (lua_State *L) {
+ TTree *tree = newleaf(L, TOpenCall);
+ luaL_argcheck(L, !lua_isnoneornil(L, 1), 1, "non-nil value expected");
+ tree->key = addtonewktable(L, 0, 1);
+ return 1;
+}
+
+
+/*
+** Create a tree for a non-empty capture, with a body and
+** optionally with an associated Lua value (at index 'labelidx' in the
+** stack)
+*/
+static int capture_aux (lua_State *L, int cap, int labelidx) {
+ TTree *tree = newroot1sib(L, TCapture);
+ tree->cap = cap;
+ tree->key = (labelidx == 0) ? 0 : addtonewktable(L, 1, labelidx);
+ return 1;
+}
+
+
+/*
+** Fill a tree with an empty capture, using an empty (TTrue) sibling.
+** (The 'key' field must be filled by the caller to finish the tree.)
+*/
+static TTree *auxemptycap (TTree *tree, int cap) {
+ tree->tag = TCapture;
+ tree->cap = cap;
+ sib1(tree)->tag = TTrue;
+ return tree;
+}
+
+
+/*
+** Create a tree for an empty capture.
+*/
+static TTree *newemptycap (lua_State *L, int cap, int key) {
+ TTree *tree = auxemptycap(newtree(L, 2), cap);
+ tree->key = key;
+ return tree;
+}
+
+
+/*
+** Create a tree for an empty capture with an associated Lua value.
+*/
+static TTree *newemptycapkey (lua_State *L, int cap, int idx) {
+ TTree *tree = auxemptycap(newtree(L, 2), cap);
+ tree->key = addtonewktable(L, 0, idx);
+ return tree;
+}
+
+
+/*
+** Captures with syntax p / v
+** (function capture, query capture, string capture, or number capture)
+*/
+static int lp_divcapture (lua_State *L) {
+ switch (lua_type(L, 2)) {
+ case LUA_TFUNCTION: return capture_aux(L, Cfunction, 2);
+ case LUA_TTABLE: return capture_aux(L, Cquery, 2);
+ case LUA_TSTRING: return capture_aux(L, Cstring, 2);
+ case LUA_TNUMBER: {
+ int n = lua_tointeger(L, 2);
+ TTree *tree = newroot1sib(L, TCapture);
+ luaL_argcheck(L, 0 <= n && n <= SHRT_MAX, 1, "invalid number");
+ tree->cap = Cnum;
+ tree->key = n;
+ return 1;
+ }
+ default: return luaL_argerror(L, 2, "invalid replacement value");
+ }
+}
+
+
+static int lp_substcapture (lua_State *L) {
+ return capture_aux(L, Csubst, 0);
+}
+
+
+static int lp_tablecapture (lua_State *L) {
+ return capture_aux(L, Ctable, 0);
+}
+
+
+static int lp_groupcapture (lua_State *L) {
+ if (lua_isnoneornil(L, 2))
+ return capture_aux(L, Cgroup, 0);
+ else
+ return capture_aux(L, Cgroup, 2);
+}
+
+
+static int lp_foldcapture (lua_State *L) {
+ luaL_checktype(L, 2, LUA_TFUNCTION);
+ return capture_aux(L, Cfold, 2);
+}
+
+
+static int lp_simplecapture (lua_State *L) {
+ return capture_aux(L, Csimple, 0);
+}
+
+
+static int lp_poscapture (lua_State *L) {
+ newemptycap(L, Cposition, 0);
+ return 1;
+}
+
+
+static int lp_argcapture (lua_State *L) {
+ int n = (int)luaL_checkinteger(L, 1);
+ luaL_argcheck(L, 0 < n && n <= SHRT_MAX, 1, "invalid argument index");
+ newemptycap(L, Carg, n);
+ return 1;
+}
+
+
+static int lp_backref (lua_State *L) {
+ luaL_checkany(L, 1);
+ newemptycapkey(L, Cbackref, 1);
+ return 1;
+}
+
+
+/*
+** Constant capture
+*/
+static int lp_constcapture (lua_State *L) {
+ int i;
+ int n = lua_gettop(L); /* number of values */
+ if (n == 0) /* no values? */
+ newleaf(L, TTrue); /* no capture */
+ else if (n == 1)
+ newemptycapkey(L, Cconst, 1); /* single constant capture */
+ else { /* create a group capture with all values */
+ TTree *tree = newtree(L, 1 + 3 * (n - 1) + 2);
+ newktable(L, n); /* create a 'ktable' for new tree */
+ tree->tag = TCapture;
+ tree->cap = Cgroup;
+ tree->key = 0;
+ tree = sib1(tree);
+ for (i = 1; i <= n - 1; i++) {
+ tree->tag = TSeq;
+ tree->u.ps = 3; /* skip TCapture and its sibling */
+ auxemptycap(sib1(tree), Cconst);
+ sib1(tree)->key = addtoktable(L, i);
+ tree = sib2(tree);
+ }
+ auxemptycap(tree, Cconst);
+ tree->key = addtoktable(L, i);
+ }
+ return 1;
+}
+
+
+static int lp_matchtime (lua_State *L) {
+ TTree *tree;
+ luaL_checktype(L, 2, LUA_TFUNCTION);
+ tree = newroot1sib(L, TRunTime);
+ tree->key = addtonewktable(L, 1, 2);
+ return 1;
+}
+
+/* }====================================================== */
+
+
+/*
+** {======================================================
+** Grammar - Tree generation
+** =======================================================
+*/
+
+/*
+** push on the stack the index and the pattern for the
+** initial rule of grammar at index 'arg' in the stack;
+** also add that index into position table.
+*/
+static void getfirstrule (lua_State *L, int arg, int postab) {
+ lua_rawgeti(L, arg, 1); /* access first element */
+ if (lua_isstring(L, -1)) { /* is it the name of initial rule? */
+ lua_pushvalue(L, -1); /* duplicate it to use as key */
+ lua_gettable(L, arg); /* get associated rule */
+ }
+ else {
+ lua_pushinteger(L, 1); /* key for initial rule */
+ lua_insert(L, -2); /* put it before rule */
+ }
+ if (!testpattern(L, -1)) { /* initial rule not a pattern? */
+ if (lua_isnil(L, -1))
+ luaL_error(L, "grammar has no initial rule");
+ else
+ luaL_error(L, "initial rule '%s' is not a pattern", lua_tostring(L, -2));
+ }
+ lua_pushvalue(L, -2); /* push key */
+ lua_pushinteger(L, 1); /* push rule position (after TGrammar) */
+ lua_settable(L, postab); /* insert pair at position table */
+}
+
+/*
+** traverse grammar at index 'arg', pushing all its keys and patterns
+** into the stack. Create a new table (before all pairs key-pattern) to
+** collect all keys and their associated positions in the final tree
+** (the "position table").
+** Return the number of rules and (in 'totalsize') the total size
+** for the new tree.
+*/
+static int collectrules (lua_State *L, int arg, int *totalsize) {
+ int n = 1; /* to count number of rules */
+ int postab = lua_gettop(L) + 1; /* index of position table */
+ int size; /* accumulator for total size */
+ lua_newtable(L); /* create position table */
+ getfirstrule(L, arg, postab);
+ size = 2 + getsize(L, postab + 2); /* TGrammar + TRule + rule */
+ lua_pushnil(L); /* prepare to traverse grammar table */
+ while (lua_next(L, arg) != 0) {
+ if (lua_tonumber(L, -2) == 1 ||
+ lp_equal(L, -2, postab + 1)) { /* initial rule? */
+ lua_pop(L, 1); /* remove value (keep key for lua_next) */
+ continue;
+ }
+ if (!testpattern(L, -1)) /* value is not a pattern? */
+ luaL_error(L, "rule '%s' is not a pattern", val2str(L, -2));
+ luaL_checkstack(L, LUA_MINSTACK, "grammar has too many rules");
+ lua_pushvalue(L, -2); /* push key (to insert into position table) */
+ lua_pushinteger(L, size);
+ lua_settable(L, postab);
+ size += 1 + getsize(L, -1); /* update size */
+ lua_pushvalue(L, -2); /* push key (for next lua_next) */
+ n++;
+ }
+ *totalsize = size + 1; /* TTrue to finish list of rules */
+ return n;
+}
+
+
+static void buildgrammar (lua_State *L, TTree *grammar, int frule, int n) {
+ int i;
+ TTree *nd = sib1(grammar); /* auxiliary pointer to traverse the tree */
+ for (i = 0; i < n; i++) { /* add each rule into new tree */
+ int ridx = frule + 2*i + 1; /* index of i-th rule */
+ int rulesize;
+ TTree *rn = gettree(L, ridx, &rulesize);
+ nd->tag = TRule;
+ nd->key = 0; /* will be fixed when rule is used */
+ nd->cap = i; /* rule number */
+ nd->u.ps = rulesize + 1; /* point to next rule */
+ memcpy(sib1(nd), rn, rulesize * sizeof(TTree)); /* copy rule */
+ mergektable(L, ridx, sib1(nd)); /* merge its ktable into new one */
+ nd = sib2(nd); /* move to next rule */
+ }
+ nd->tag = TTrue; /* finish list of rules */
+}
+
+
+/*
+** Check whether a tree has potential infinite loops
+*/
+static int checkloops (TTree *tree) {
+ tailcall:
+ if (tree->tag == TRep && nullable(sib1(tree)))
+ return 1;
+ else if (tree->tag == TGrammar)
+ return 0; /* sub-grammars already checked */
+ else {
+ switch (numsiblings[tree->tag]) {
+ case 1: /* return checkloops(sib1(tree)); */
+ tree = sib1(tree); goto tailcall;
+ case 2:
+ if (checkloops(sib1(tree))) return 1;
+ /* else return checkloops(sib2(tree)); */
+ tree = sib2(tree); goto tailcall;
+ default: assert(numsiblings[tree->tag] == 0); return 0;
+ }
+ }
+}
+
+
+/*
+** Give appropriate error message for 'verifyrule'. If a rule appears
+** twice in 'passed', there is path from it back to itself without
+** advancing the subject.
+*/
+static int verifyerror (lua_State *L, int *passed, int npassed) {
+ int i, j;
+ for (i = npassed - 1; i >= 0; i--) { /* search for a repetition */
+ for (j = i - 1; j >= 0; j--) {
+ if (passed[i] == passed[j]) {
+ lua_rawgeti(L, -1, passed[i]); /* get rule's key */
+ return luaL_error(L, "rule '%s' may be left recursive", val2str(L, -1));
+ }
+ }
+ }
+ return luaL_error(L, "too many left calls in grammar");
+}
+
+
+/*
+** Check whether a rule can be left recursive; raise an error in that
+** case; otherwise return 1 iff pattern is nullable.
+** The return value is used to check sequences, where the second pattern
+** is only relevant if the first is nullable.
+** Parameter 'nb' works as an accumulator, to allow tail calls in
+** choices. ('nb' true makes function returns true.)
+** Parameter 'passed' is a list of already visited rules, 'npassed'
+** counts the elements in 'passed'.
+** Assume ktable at the top of the stack.
+*/
+static int verifyrule (lua_State *L, TTree *tree, int *passed, int npassed,
+ int nb) {
+ tailcall:
+ switch (tree->tag) {
+ case TChar: case TSet: case TAny:
+ case TFalse:
+ return nb; /* cannot pass from here */
+ case TTrue:
+ case TBehind: /* look-behind cannot have calls */
+ return 1;
+ case TNot: case TAnd: case TRep:
+ /* return verifyrule(L, sib1(tree), passed, npassed, 1); */
+ tree = sib1(tree); nb = 1; goto tailcall;
+ case TCapture: case TRunTime:
+ /* return verifyrule(L, sib1(tree), passed, npassed, nb); */
+ tree = sib1(tree); goto tailcall;
+ case TCall:
+ /* return verifyrule(L, sib2(tree), passed, npassed, nb); */
+ tree = sib2(tree); goto tailcall;
+ case TSeq: /* only check 2nd child if first is nb */
+ if (!verifyrule(L, sib1(tree), passed, npassed, 0))
+ return nb;
+ /* else return verifyrule(L, sib2(tree), passed, npassed, nb); */
+ tree = sib2(tree); goto tailcall;
+ case TChoice: /* must check both children */
+ nb = verifyrule(L, sib1(tree), passed, npassed, nb);
+ /* return verifyrule(L, sib2(tree), passed, npassed, nb); */
+ tree = sib2(tree); goto tailcall;
+ case TRule:
+ if (npassed >= MAXRULES)
+ return verifyerror(L, passed, npassed);
+ else {
+ passed[npassed++] = tree->key;
+ /* return verifyrule(L, sib1(tree), passed, npassed); */
+ tree = sib1(tree); goto tailcall;
+ }
+ case TGrammar:
+ return nullable(tree); /* sub-grammar cannot be left recursive */
+ default: assert(0); return 0;
+ }
+}
+
+
+static void verifygrammar (lua_State *L, TTree *grammar) {
+ int passed[MAXRULES];
+ TTree *rule;
+ /* check left-recursive rules */
+ for (rule = sib1(grammar); rule->tag == TRule; rule = sib2(rule)) {
+ if (rule->key == 0) continue; /* unused rule */
+ verifyrule(L, sib1(rule), passed, 0, 0);
+ }
+ assert(rule->tag == TTrue);
+ /* check infinite loops inside rules */
+ for (rule = sib1(grammar); rule->tag == TRule; rule = sib2(rule)) {
+ if (rule->key == 0) continue; /* unused rule */
+ if (checkloops(sib1(rule))) {
+ lua_rawgeti(L, -1, rule->key); /* get rule's key */
+ luaL_error(L, "empty loop in rule '%s'", val2str(L, -1));
+ }
+ }
+ assert(rule->tag == TTrue);
+}
+
+
+/*
+** Give a name for the initial rule if it is not referenced
+*/
+static void initialrulename (lua_State *L, TTree *grammar, int frule) {
+ if (sib1(grammar)->key == 0) { /* initial rule is not referenced? */
+ int n = lua_rawlen(L, -1) + 1; /* index for name */
+ lua_pushvalue(L, frule); /* rule's name */
+ lua_rawseti(L, -2, n); /* ktable was on the top of the stack */
+ sib1(grammar)->key = n;
+ }
+}
+
+
+static TTree *newgrammar (lua_State *L, int arg) {
+ int treesize;
+ int frule = lua_gettop(L) + 2; /* position of first rule's key */
+ int n = collectrules(L, arg, &treesize);
+ TTree *g = newtree(L, treesize);
+ luaL_argcheck(L, n <= MAXRULES, arg, "grammar has too many rules");
+ g->tag = TGrammar; g->u.n = n;
+ lua_newtable(L); /* create 'ktable' */
+ lua_setuservalue(L, -2);
+ buildgrammar(L, g, frule, n);
+ lua_getuservalue(L, -1); /* get 'ktable' for new tree */
+ finalfix(L, frule - 1, g, sib1(g));
+ initialrulename(L, g, frule);
+ verifygrammar(L, g);
+ lua_pop(L, 1); /* remove 'ktable' */
+ lua_insert(L, -(n * 2 + 2)); /* move new table to proper position */
+ lua_pop(L, n * 2 + 1); /* remove position table + rule pairs */
+ return g; /* new table at the top of the stack */
+}
+
+/* }====================================================== */
+
+
+static Instruction *prepcompile (lua_State *L, Pattern *p, int idx) {
+ lua_getuservalue(L, idx); /* push 'ktable' (may be used by 'finalfix') */
+ finalfix(L, 0, NULL, p->tree);
+ lua_pop(L, 1); /* remove 'ktable' */
+ return compile(L, p);
+}
+
+
+static int lp_printtree (lua_State *L) {
+ TTree *tree = getpatt(L, 1, NULL);
+ int c = lua_toboolean(L, 2);
+ if (c) {
+ lua_getuservalue(L, 1); /* push 'ktable' (may be used by 'finalfix') */
+ finalfix(L, 0, NULL, tree);
+ lua_pop(L, 1); /* remove 'ktable' */
+ }
+ printktable(L, 1);
+ printtree(tree, 0);
+ return 0;
+}
+
+
+static int lp_printcode (lua_State *L) {
+ Pattern *p = getpattern(L, 1);
+ printktable(L, 1);
+ if (p->code == NULL) /* not compiled yet? */
+ prepcompile(L, p, 1);
+ printpatt(p->code, p->codesize);
+ return 0;
+}
+
+
+/*
+** Get the initial position for the match, interpreting negative
+** values from the end of the subject
+*/
+static size_t initposition (lua_State *L, size_t len) {
+ lua_Integer ii = luaL_optinteger(L, 3, 1);
+ if (ii > 0) { /* positive index? */
+ if ((size_t)ii <= len) /* inside the string? */
+ return (size_t)ii - 1; /* return it (corrected to 0-base) */
+ else return len; /* crop at the end */
+ }
+ else { /* negative index */
+ if ((size_t)(-ii) <= len) /* inside the string? */
+ return len - ((size_t)(-ii)); /* return position from the end */
+ else return 0; /* crop at the beginning */
+ }
+}
+
+
+/*
+** Main match function
+*/
+static int lp_match (lua_State *L) {
+ Capture capture[INITCAPSIZE];
+ const char *r;
+ size_t l;
+ Pattern *p = (getpatt(L, 1, NULL), getpattern(L, 1));
+ Instruction *code = (p->code != NULL) ? p->code : prepcompile(L, p, 1);
+ const char *s = luaL_checklstring(L, SUBJIDX, &l);
+ size_t i = initposition(L, l);
+ int ptop = lua_gettop(L);
+ lua_pushnil(L); /* initialize subscache */
+ lua_pushlightuserdata(L, capture); /* initialize caplistidx */
+ lua_getuservalue(L, 1); /* initialize penvidx */
+ r = match(L, s, s + i, s + l, code, capture, ptop);
+ if (r == NULL) {
+ lua_pushnil(L);
+ return 1;
+ }
+ return getcaptures(L, s, r, ptop);
+}
+
+
+
+/*
+** {======================================================
+** Library creation and functions not related to matching
+** =======================================================
+*/
+
+/* maximum limit for stack size */
+#define MAXLIM (INT_MAX / 100)
+
+static int lp_setmax (lua_State *L) {
+ lua_Integer lim = luaL_checkinteger(L, 1);
+ luaL_argcheck(L, 0 < lim && lim <= MAXLIM, 1, "out of range");
+ lua_settop(L, 1);
+ lua_setfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX);
+ return 0;
+}
+
+
+static int lp_version (lua_State *L) {
+ lua_pushstring(L, VERSION);
+ return 1;
+}
+
+
+static int lp_type (lua_State *L) {
+ if (testpattern(L, 1))
+ lua_pushliteral(L, "pattern");
+ else
+ lua_pushnil(L);
+ return 1;
+}
+
+
+int lp_gc (lua_State *L) {
+ Pattern *p = getpattern(L, 1);
+ realloccode(L, p, 0); /* delete code block */
+ return 0;
+}
+
+
+static void createcat (lua_State *L, const char *catname, int (catf) (int)) {
+ TTree *t = newcharset(L);
+ int i;
+ for (i = 0; i <= UCHAR_MAX; i++)
+ if (catf(i)) setchar(treebuffer(t), i);
+ lua_setfield(L, -2, catname);
+}
+
+
+static int lp_locale (lua_State *L) {
+ if (lua_isnoneornil(L, 1)) {
+ lua_settop(L, 0);
+ lua_createtable(L, 0, 12);
+ }
+ else {
+ luaL_checktype(L, 1, LUA_TTABLE);
+ lua_settop(L, 1);
+ }
+ createcat(L, "alnum", isalnum);
+ createcat(L, "alpha", isalpha);
+ createcat(L, "cntrl", iscntrl);
+ createcat(L, "digit", isdigit);
+ createcat(L, "graph", isgraph);
+ createcat(L, "lower", islower);
+ createcat(L, "print", isprint);
+ createcat(L, "punct", ispunct);
+ createcat(L, "space", isspace);
+ createcat(L, "upper", isupper);
+ createcat(L, "xdigit", isxdigit);
+ return 1;
+}
+
+
+static struct luaL_Reg pattreg[] = {
+ {"ptree", lp_printtree},
+ {"pcode", lp_printcode},
+ {"match", lp_match},
+ {"B", lp_behind},
+ {"V", lp_V},
+ {"C", lp_simplecapture},
+ {"Cc", lp_constcapture},
+ {"Cmt", lp_matchtime},
+ {"Cb", lp_backref},
+ {"Carg", lp_argcapture},
+ {"Cp", lp_poscapture},
+ {"Cs", lp_substcapture},
+ {"Ct", lp_tablecapture},
+ {"Cf", lp_foldcapture},
+ {"Cg", lp_groupcapture},
+ {"P", lp_P},
+ {"S", lp_set},
+ {"R", lp_range},
+ {"locale", lp_locale},
+ {"version", lp_version},
+ {"setmaxstack", lp_setmax},
+ {"type", lp_type},
+ {NULL, NULL}
+};
+
+
+static struct luaL_Reg metareg[] = {
+ {"__mul", lp_seq},
+ {"__add", lp_choice},
+ {"__pow", lp_star},
+ {"__gc", lp_gc},
+ {"__len", lp_and},
+ {"__div", lp_divcapture},
+ {"__unm", lp_not},
+ {"__sub", lp_sub},
+ {NULL, NULL}
+};
+
+
+int luaopen_lpeg (lua_State *L);
+int luaopen_lpeg (lua_State *L) {
+ luaL_newmetatable(L, PATTERN_T);
+ lua_pushnumber(L, MAXBACK); /* initialize maximum backtracking */
+ lua_setfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX);
+ luaL_setfuncs(L, metareg, 0);
+ luaL_newlib(L, pattreg);
+ lua_pushvalue(L, -1);
+ lua_setfield(L, -3, "__index");
+ return 1;
+}
+
+/* }====================================================== */
diff --git a/luaclib/src/lpeg/lptree.h b/luaclib/src/lpeg/lptree.h
new file mode 100644
index 00000000..25906d5f
--- /dev/null
+++ b/luaclib/src/lpeg/lptree.h
@@ -0,0 +1,82 @@
+/*
+** $Id: lptree.h $
+*/
+
+#if !defined(lptree_h)
+#define lptree_h
+
+
+#include "lptypes.h"
+
+
+/*
+** types of trees
+*/
+typedef enum TTag {
+ TChar = 0, /* 'n' = char */
+ TSet, /* the set is stored in next CHARSETSIZE bytes */
+ TAny,
+ TTrue,
+ TFalse,
+ TRep, /* 'sib1'* */
+ TSeq, /* 'sib1' 'sib2' */
+ TChoice, /* 'sib1' / 'sib2' */
+ TNot, /* !'sib1' */
+ TAnd, /* &'sib1' */
+ TCall, /* ktable[key] is rule's key; 'sib2' is rule being called */
+ TOpenCall, /* ktable[key] is rule's key */
+ TRule, /* ktable[key] is rule's key (but key == 0 for unused rules);
+ 'sib1' is rule's pattern;
+ 'sib2' is next rule; 'cap' is rule's sequential number */
+ TGrammar, /* 'sib1' is initial (and first) rule */
+ TBehind, /* 'sib1' is pattern, 'n' is how much to go back */
+ TCapture, /* captures: 'cap' is kind of capture (enum 'CapKind');
+ ktable[key] is Lua value associated with capture;
+ 'sib1' is capture body */
+ TRunTime /* run-time capture: 'key' is Lua function;
+ 'sib1' is capture body */
+} TTag;
+
+
+/*
+** Tree trees
+** The first child of a tree (if there is one) is immediately after
+** the tree. A reference to a second child (ps) is its position
+** relative to the position of the tree itself.
+*/
+typedef struct TTree {
+ byte tag;
+ byte cap; /* kind of capture (if it is a capture) */
+ unsigned short key; /* key in ktable for Lua data (0 if no key) */
+ union {
+ int ps; /* occasional second child */
+ int n; /* occasional counter */
+ } u;
+} TTree;
+
+
+/*
+** A complete pattern has its tree plus, if already compiled,
+** its corresponding code
+*/
+typedef struct Pattern {
+ union Instruction *code;
+ int codesize;
+ TTree tree[1];
+} Pattern;
+
+
+/* number of children for each tree */
+extern const byte numsiblings[];
+
+/* access to children */
+#define sib1(t) ((t) + 1)
+#define sib2(t) ((t) + (t)->u.ps)
+
+
+
+
+
+
+#endif
+
diff --git a/luaclib/src/lpeg/lptypes.h b/luaclib/src/lpeg/lptypes.h
new file mode 100644
index 00000000..1d9d59f6
--- /dev/null
+++ b/luaclib/src/lpeg/lptypes.h
@@ -0,0 +1,145 @@
+/*
+** $Id: lptypes.h $
+** LPeg - PEG pattern matching for Lua
+** Copyright 2007-2019, Lua.org & PUC-Rio (see 'lpeg.html' for license)
+** written by Roberto Ierusalimschy
+*/
+
+#if !defined(lptypes_h)
+#define lptypes_h
+
+
+#include
+#include
+
+#include "lua.h"
+
+
+#define VERSION "1.0.2"
+
+
+#define PATTERN_T "lpeg-pattern"
+#define MAXSTACKIDX "lpeg-maxstack"
+
+
+/*
+** compatibility with Lua 5.1
+*/
+#if (LUA_VERSION_NUM == 501)
+
+#define lp_equal lua_equal
+
+#define lua_getuservalue lua_getfenv
+#define lua_setuservalue lua_setfenv
+
+#define lua_rawlen lua_objlen
+
+#define luaL_setfuncs(L,f,n) luaL_register(L,NULL,f)
+#define luaL_newlib(L,f) luaL_register(L,"lpeg",f)
+
+#endif
+
+
+#if !defined(lp_equal)
+#define lp_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ)
+#endif
+
+
+/* default maximum size for call/backtrack stack */
+#if !defined(MAXBACK)
+#define MAXBACK 400
+#endif
+
+
+/* maximum number of rules in a grammar (limited by 'unsigned char') */
+#if !defined(MAXRULES)
+#define MAXRULES 250
+#endif
+
+
+
+/* initial size for capture's list */
+#define INITCAPSIZE 32
+
+
+/* index, on Lua stack, for subject */
+#define SUBJIDX 2
+
+/* number of fixed arguments to 'match' (before capture arguments) */
+#define FIXEDARGS 3
+
+/* index, on Lua stack, for capture list */
+#define caplistidx(ptop) ((ptop) + 2)
+
+/* index, on Lua stack, for pattern's ktable */
+#define ktableidx(ptop) ((ptop) + 3)
+
+/* index, on Lua stack, for backtracking stack */
+#define stackidx(ptop) ((ptop) + 4)
+
+
+
+typedef unsigned char byte;
+
+
+#define BITSPERCHAR 8
+
+#define CHARSETSIZE ((UCHAR_MAX/BITSPERCHAR) + 1)
+
+
+
+typedef struct Charset {
+ byte cs[CHARSETSIZE];
+} Charset;
+
+
+
+#define loopset(v,b) { int v; for (v = 0; v < CHARSETSIZE; v++) {b;} }
+
+/* access to charset */
+#define treebuffer(t) ((byte *)((t) + 1))
+
+/* number of slots needed for 'n' bytes */
+#define bytes2slots(n) (((n) - 1) / sizeof(TTree) + 1)
+
+/* set 'b' bit in charset 'cs' */
+#define setchar(cs,b) ((cs)[(b) >> 3] |= (1 << ((b) & 7)))
+
+
+/*
+** in capture instructions, 'kind' of capture and its offset are
+** packed in field 'aux', 4 bits for each
+*/
+#define getkind(op) ((op)->i.aux & 0xF)
+#define getoff(op) (((op)->i.aux >> 4) & 0xF)
+#define joinkindoff(k,o) ((k) | ((o) << 4))
+
+#define MAXOFF 0xF
+#define MAXAUX 0xFF
+
+
+/* maximum number of bytes to look behind */
+#define MAXBEHIND MAXAUX
+
+
+/* maximum size (in elements) for a pattern */
+#define MAXPATTSIZE (SHRT_MAX - 10)
+
+
+/* size (in elements) for an instruction plus extra l bytes */
+#define instsize(l) (((l) + sizeof(Instruction) - 1)/sizeof(Instruction) + 1)
+
+
+/* size (in elements) for a ISet instruction */
+#define CHARSETINSTSIZE instsize(CHARSETSIZE)
+
+/* size (in elements) for a IFunc instruction */
+#define funcinstsize(p) ((p)->i.aux + 2)
+
+
+
+#define testchar(st,c) (((int)(st)[((c) >> 3)] & (1 << ((c) & 7))))
+
+
+#endif
+
diff --git a/luaclib/src/lpeg/lpvm.c b/luaclib/src/lpeg/lpvm.c
new file mode 100644
index 00000000..516f2cca
--- /dev/null
+++ b/luaclib/src/lpeg/lpvm.c
@@ -0,0 +1,369 @@
+/*
+** $Id: lpvm.c $
+** Copyright 2007, Lua.org & PUC-Rio (see 'lpeg.html' for license)
+*/
+
+#include
+
+#include "lpcap.h"
+#include "lptypes.h"
+#include "lpvm.h"
+#include "lpprint.h"
+
+
+/* initial size for call/backtrack stack */
+#if !defined(INITBACK)
+#define INITBACK MAXBACK
+#endif
+
+
+#define getoffset(p) (((p) + 1)->offset)
+
+static const Instruction giveup = {{IGiveup, 0, 0}};
+
+
+/*
+** {======================================================
+** Virtual Machine
+** =======================================================
+*/
+
+
+typedef struct Stack {
+ const char *s; /* saved position (or NULL for calls) */
+ const Instruction *p; /* next instruction */
+ int caplevel;
+} Stack;
+
+
+#define getstackbase(L, ptop) ((Stack *)lua_touserdata(L, stackidx(ptop)))
+
+
+/*
+** Ensures the size of array 'capture' (with size '*capsize' and
+** 'captop' elements being used) is enough to accomodate 'n' extra
+** elements plus one. (Because several opcodes add stuff to the capture
+** array, it is simpler to ensure the array always has at least one free
+** slot upfront and check its size later.)
+*/
+static Capture *growcap (lua_State *L, Capture *capture, int *capsize,
+ int captop, int n, int ptop) {
+ if (*capsize - captop > n)
+ return capture; /* no need to grow array */
+ else { /* must grow */
+ Capture *newc;
+ int newsize = captop + n + 1; /* minimum size needed */
+ if (newsize < INT_MAX/((int)sizeof(Capture) * 2))
+ newsize *= 2; /* twice that size, if not too big */
+ else if (newsize >= INT_MAX/((int)sizeof(Capture)))
+ luaL_error(L, "too many captures");
+ newc = (Capture *)lua_newuserdata(L, newsize * sizeof(Capture));
+ memcpy(newc, capture, captop * sizeof(Capture));
+ *capsize = newsize;
+ lua_replace(L, caplistidx(ptop));
+ return newc;
+ }
+}
+
+
+/*
+** Double the size of the stack
+*/
+static Stack *doublestack (lua_State *L, Stack **stacklimit, int ptop) {
+ Stack *stack = getstackbase(L, ptop);
+ Stack *newstack;
+ int n = *stacklimit - stack; /* current stack size */
+ int max, newn;
+ lua_getfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX);
+ max = lua_tointeger(L, -1); /* maximum allowed size */
+ lua_pop(L, 1);
+ if (n >= max) /* already at maximum size? */
+ luaL_error(L, "backtrack stack overflow (current limit is %d)", max);
+ newn = 2 * n; /* new size */
+ if (newn > max) newn = max;
+ newstack = (Stack *)lua_newuserdata(L, newn * sizeof(Stack));
+ memcpy(newstack, stack, n * sizeof(Stack));
+ lua_replace(L, stackidx(ptop));
+ *stacklimit = newstack + newn;
+ return newstack + n; /* return next position */
+}
+
+
+/*
+** Interpret the result of a dynamic capture: false -> fail;
+** true -> keep current position; number -> next position.
+** Return new subject position. 'fr' is stack index where
+** is the result; 'curr' is current subject position; 'limit'
+** is subject's size.
+*/
+static int resdyncaptures (lua_State *L, int fr, int curr, int limit) {
+ lua_Integer res;
+ if (!lua_toboolean(L, fr)) { /* false value? */
+ lua_settop(L, fr - 1); /* remove results */
+ return -1; /* and fail */
+ }
+ else if (lua_isboolean(L, fr)) /* true? */
+ res = curr; /* keep current position */
+ else {
+ res = lua_tointeger(L, fr) - 1; /* new position */
+ if (res < curr || res > limit)
+ luaL_error(L, "invalid position returned by match-time capture");
+ }
+ lua_remove(L, fr); /* remove first result (offset) */
+ return res;
+}
+
+
+/*
+** Add capture values returned by a dynamic capture to the list
+** 'capture', nested inside a group. 'fd' indexes the first capture
+** value, 'n' is the number of values (at least 1). The open group
+** capture is already in 'capture', before the place for the new entries.
+*/
+static void adddyncaptures (const char *s, Capture *capture, int n, int fd) {
+ int i;
+ assert(capture[-1].kind == Cgroup && capture[-1].siz == 0);
+ capture[-1].idx = 0; /* make group capture an anonymous group */
+ for (i = 0; i < n; i++) { /* add runtime captures */
+ capture[i].kind = Cruntime;
+ capture[i].siz = 1; /* mark it as closed */
+ capture[i].idx = fd + i; /* stack index of capture value */
+ capture[i].s = s;
+ }
+ capture[n].kind = Cclose; /* close group */
+ capture[n].siz = 1;
+ capture[n].s = s;
+}
+
+
+/*
+** Remove dynamic captures from the Lua stack (called in case of failure)
+*/
+static int removedyncap (lua_State *L, Capture *capture,
+ int level, int last) {
+ int id = finddyncap(capture + level, capture + last); /* index of 1st cap. */
+ int top = lua_gettop(L);
+ if (id == 0) return 0; /* no dynamic captures? */
+ lua_settop(L, id - 1); /* remove captures */
+ return top - id + 1; /* number of values removed */
+}
+
+
+/*
+** Opcode interpreter
+*/
+const char *match (lua_State *L, const char *o, const char *s, const char *e,
+ Instruction *op, Capture *capture, int ptop) {
+ Stack stackbase[INITBACK];
+ Stack *stacklimit = stackbase + INITBACK;
+ Stack *stack = stackbase; /* point to first empty slot in stack */
+ int capsize = INITCAPSIZE;
+ int captop = 0; /* point to first empty slot in captures */
+ int ndyncap = 0; /* number of dynamic captures (in Lua stack) */
+ const Instruction *p = op; /* current instruction */
+ stack->p = &giveup; stack->s = s; stack->caplevel = 0; stack++;
+ lua_pushlightuserdata(L, stackbase);
+ for (;;) {
+// #if defined(DEBUG)
+// printf("-------------------------------------\n");
+// printcaplist(capture, capture + captop);
+// printf("s: |%s| stck:%d, dyncaps:%d, caps:%d ",
+// s, (int)(stack - getstackbase(L, ptop)), ndyncap, captop);
+// printinst(op, p);
+// #endif
+ assert(stackidx(ptop) + ndyncap == lua_gettop(L) && ndyncap <= captop);
+ switch ((Opcode)p->i.code) {
+ case IEnd: {
+ assert(stack == getstackbase(L, ptop) + 1);
+ capture[captop].kind = Cclose;
+ capture[captop].s = NULL;
+ return s;
+ }
+ case IGiveup: {
+ assert(stack == getstackbase(L, ptop));
+ return NULL;
+ }
+ case IRet: {
+ assert(stack > getstackbase(L, ptop) && (stack - 1)->s == NULL);
+ p = (--stack)->p;
+ continue;
+ }
+ case IAny: {
+ if (s < e) { p++; s++; }
+ else goto fail;
+ continue;
+ }
+ case ITestAny: {
+ if (s < e) p += 2;
+ else p += getoffset(p);
+ continue;
+ }
+ case IChar: {
+ if ((byte)*s == p->i.aux && s < e) { p++; s++; }
+ else goto fail;
+ continue;
+ }
+ case ITestChar: {
+ if ((byte)*s == p->i.aux && s < e) p += 2;
+ else p += getoffset(p);
+ continue;
+ }
+ case ISet: {
+ int c = (byte)*s;
+ if (testchar((p+1)->buff, c) && s < e)
+ { p += CHARSETINSTSIZE; s++; }
+ else goto fail;
+ continue;
+ }
+ case ITestSet: {
+ int c = (byte)*s;
+ if (testchar((p + 2)->buff, c) && s < e)
+ p += 1 + CHARSETINSTSIZE;
+ else p += getoffset(p);
+ continue;
+ }
+ case IBehind: {
+ int n = p->i.aux;
+ if (n > s - o) goto fail;
+ s -= n; p++;
+ continue;
+ }
+ case ISpan: {
+ for (; s < e; s++) {
+ int c = (byte)*s;
+ if (!testchar((p+1)->buff, c)) break;
+ }
+ p += CHARSETINSTSIZE;
+ continue;
+ }
+ case IJmp: {
+ p += getoffset(p);
+ continue;
+ }
+ case IChoice: {
+ if (stack == stacklimit)
+ stack = doublestack(L, &stacklimit, ptop);
+ stack->p = p + getoffset(p);
+ stack->s = s;
+ stack->caplevel = captop;
+ stack++;
+ p += 2;
+ continue;
+ }
+ case ICall: {
+ if (stack == stacklimit)
+ stack = doublestack(L, &stacklimit, ptop);
+ stack->s = NULL;
+ stack->p = p + 2; /* save return address */
+ stack++;
+ p += getoffset(p);
+ continue;
+ }
+ case ICommit: {
+ assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL);
+ stack--;
+ p += getoffset(p);
+ continue;
+ }
+ case IPartialCommit: {
+ assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL);
+ (stack - 1)->s = s;
+ (stack - 1)->caplevel = captop;
+ p += getoffset(p);
+ continue;
+ }
+ case IBackCommit: {
+ assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL);
+ s = (--stack)->s;
+ captop = stack->caplevel;
+ p += getoffset(p);
+ continue;
+ }
+ case IFailTwice:
+ assert(stack > getstackbase(L, ptop));
+ stack--;
+ /* go through */
+ case IFail:
+ fail: { /* pattern failed: try to backtrack */
+ do { /* remove pending calls */
+ assert(stack > getstackbase(L, ptop));
+ s = (--stack)->s;
+ } while (s == NULL);
+ if (ndyncap > 0) /* is there matchtime captures? */
+ ndyncap -= removedyncap(L, capture, stack->caplevel, captop);
+ captop = stack->caplevel;
+ p = stack->p;
+#if defined(DEBUG)
+ printf("**FAIL**\n");
+#endif
+ continue;
+ }
+ case ICloseRunTime: {
+ CapState cs;
+ int rem, res, n;
+ int fr = lua_gettop(L) + 1; /* stack index of first result */
+ cs.reclevel = 0; cs.L = L;
+ cs.s = o; cs.ocap = capture; cs.ptop = ptop;
+ n = runtimecap(&cs, capture + captop, s, &rem); /* call function */
+ captop -= n; /* remove nested captures */
+ ndyncap -= rem; /* update number of dynamic captures */
+ fr -= rem; /* 'rem' items were popped from Lua stack */
+ res = resdyncaptures(L, fr, s - o, e - o); /* get result */
+ if (res == -1) /* fail? */
+ goto fail;
+ s = o + res; /* else update current position */
+ n = lua_gettop(L) - fr + 1; /* number of new captures */
+ ndyncap += n; /* update number of dynamic captures */
+ if (n == 0) /* no new captures? */
+ captop--; /* remove open group */
+ else { /* new captures; keep original open group */
+ if (fr + n >= SHRT_MAX)
+ luaL_error(L, "too many results in match-time capture");
+ /* add new captures + close group to 'capture' list */
+ capture = growcap(L, capture, &capsize, captop, n + 1, ptop);
+ adddyncaptures(s, capture + captop, n, fr);
+ captop += n + 1; /* new captures + close group */
+ }
+ p++;
+ continue;
+ }
+ case ICloseCapture: {
+ const char *s1 = s;
+ assert(captop > 0);
+ /* if possible, turn capture into a full capture */
+ if (capture[captop - 1].siz == 0 &&
+ s1 - capture[captop - 1].s < UCHAR_MAX) {
+ capture[captop - 1].siz = s1 - capture[captop - 1].s + 1;
+ p++;
+ continue;
+ }
+ else {
+ capture[captop].siz = 1; /* mark entry as closed */
+ capture[captop].s = s;
+ goto pushcapture;
+ }
+ }
+ case IOpenCapture:
+ capture[captop].siz = 0; /* mark entry as open */
+ capture[captop].s = s;
+ goto pushcapture;
+ case IFullCapture:
+ capture[captop].siz = getoff(p) + 1; /* save capture size */
+ capture[captop].s = s - getoff(p);
+ /* goto pushcapture; */
+ pushcapture: {
+ capture[captop].idx = p->i.key;
+ capture[captop].kind = getkind(p);
+ captop++;
+ capture = growcap(L, capture, &capsize, captop, 0, ptop);
+ p++;
+ continue;
+ }
+ default: assert(0); return NULL;
+ }
+ }
+}
+
+/* }====================================================== */
+
+
diff --git a/luaclib/src/lpeg/lpvm.h b/luaclib/src/lpeg/lpvm.h
new file mode 100644
index 00000000..69ec33dc
--- /dev/null
+++ b/luaclib/src/lpeg/lpvm.h
@@ -0,0 +1,58 @@
+/*
+** $Id: lpvm.h $
+*/
+
+#if !defined(lpvm_h)
+#define lpvm_h
+
+#include "lpcap.h"
+
+
+/* Virtual Machine's instructions */
+typedef enum Opcode {
+ IAny, /* if no char, fail */
+ IChar, /* if char != aux, fail */
+ ISet, /* if char not in buff, fail */
+ ITestAny, /* in no char, jump to 'offset' */
+ ITestChar, /* if char != aux, jump to 'offset' */
+ ITestSet, /* if char not in buff, jump to 'offset' */
+ ISpan, /* read a span of chars in buff */
+ IBehind, /* walk back 'aux' characters (fail if not possible) */
+ IRet, /* return from a rule */
+ IEnd, /* end of pattern */
+ IChoice, /* stack a choice; next fail will jump to 'offset' */
+ IJmp, /* jump to 'offset' */
+ ICall, /* call rule at 'offset' */
+ IOpenCall, /* call rule number 'key' (must be closed to a ICall) */
+ ICommit, /* pop choice and jump to 'offset' */
+ IPartialCommit, /* update top choice to current position and jump */
+ IBackCommit, /* "fails" but jump to its own 'offset' */
+ IFailTwice, /* pop one choice and then fail */
+ IFail, /* go back to saved state on choice and jump to saved offset */
+ IGiveup, /* internal use */
+ IFullCapture, /* complete capture of last 'off' chars */
+ IOpenCapture, /* start a capture */
+ ICloseCapture,
+ ICloseRunTime
+} Opcode;
+
+
+
+typedef union Instruction {
+ struct Inst {
+ byte code;
+ byte aux;
+ short key;
+ } i;
+ int offset;
+ byte buff[1];
+} Instruction;
+
+
+void printpatt (Instruction *p, int n);
+const char *match (lua_State *L, const char *o, const char *s, const char *e,
+ Instruction *op, Capture *capture, int ptop);
+
+
+#endif
+
diff --git a/luaclib/src/lpeg/makefile b/luaclib/src/lpeg/makefile
new file mode 100644
index 00000000..8584447a
--- /dev/null
+++ b/luaclib/src/lpeg/makefile
@@ -0,0 +1,18 @@
+.PHONY : build rebuild clean
+
+default :
+ @echo "======================================="
+ @echo "Please use 'make build' command to build it.."
+ @echo "Please use 'make rebuild' command to build it.."
+ @echo "Please use 'make clean' command to clean all."
+ @echo "======================================="
+
+CC = cc
+INCLUDES += -I../../../src -I/usr/local/include
+LIBS = -L../ -L../../ -L../../../ -L/usr/local/lib
+CFLAGS = -O3 -shared -fPIC
+DLL = -lcore
+
+build:
+ @$(CC) -o lpeg.so lpcap.c lpcode.c lpprint.c lptree.c lpvm.c $(CFLAGS) $(INCLUDES) $(LIBS) $(DLL)
+ @mv *.so ../../
diff --git a/luaclib/src/lpeg/re.html b/luaclib/src/lpeg/re.html
new file mode 100644
index 00000000..ad60d509
--- /dev/null
+++ b/luaclib/src/lpeg/re.html
@@ -0,0 +1,494 @@
+
+
+
+ LPeg.re - Regex syntax for LPEG
+
+
+
+
+
+
+
+
+The re module
+(provided by file re.lua in the distribution)
+supports a somewhat conventional regex syntax
+for pattern usage within LPeg.
+
+
+
+The next table summarizes re's syntax.
+A p represents an arbitrary pattern;
+num represents a number ([0-9]+);
+name represents an identifier
+([a-zA-Z][a-zA-Z0-9_]*).
+Constructions are listed in order of decreasing precedence.
+
+
Syntax
Description
+
( p )
grouping
+
'string'
literal string
+
"string"
literal string
+
[class]
character class
+
.
any character
+
%name
+
pattern defs[name] or a pre-defined pattern
+
name
non terminal
+
<name>
non terminal
+
{}
position capture
+
{ p }
simple capture
+
{: p :}
anonymous group capture
+
{:name: p :}
named group capture
+
{~ p ~}
substitution capture
+
{| p |}
table capture
+
=name
back reference
+
+
p ?
optional match
+
p *
zero or more repetitions
+
p +
one or more repetitions
+
p^num
exactly n repetitions
+
p^+num
+
at least n repetitions
+
p^-num
+
at most n repetitions
+
p -> 'string'
string capture
+
p -> "string"
string capture
+
p -> num
numbered capture
+
p -> name
function/query/string capture
+equivalent to p / defs[name]
+
p => name
match-time capture
+equivalent to lpeg.Cmt(p, defs[name])
+
p ~> name
fold capture
+equivalent to lpeg.Cf(p, defs[name])
+
& p
and predicate
+
! p
not predicate
+
p1 p2
concatenation
+
p1 / p2
ordered choice
+
(name <- p)+
grammar
+
+
+Any space appearing in a syntax description can be
+replaced by zero or more space characters and Lua-style comments
+(-- until end of line).
+
+
+
+Character classes define sets of characters.
+An initial ^ complements the resulting set.
+A range x-y includes in the set
+all characters with codes between the codes of x and y.
+A pre-defined class %name includes all
+characters of that class.
+A simple character includes itself in the set.
+The only special characters inside a class are ^
+(special only if it is the first character);
+]
+(can be included in the set as the first character,
+after the optional ^);
+% (special only if followed by a letter);
+and -
+(can be included in the set as the first or the last character).
+
+
+
+Currently the pre-defined classes are similar to those from the
+Lua's string library
+(%a for letters,
+%A for non letters, etc.).
+There is also a class %nl
+containing only the newline character,
+which is particularly handy for grammars written inside long strings,
+as long strings do not interpret escape sequences like \n.
+
+Compiles the given string and
+returns an equivalent LPeg pattern.
+The given string may define either an expression or a grammar.
+The optional defs table provides extra Lua values
+to be used by the pattern.
+
+
+
re.find (subject, pattern [, init])
+
+Searches the given pattern in the given subject.
+If it finds a match,
+returns the index where this occurrence starts and
+the index where it ends.
+Otherwise, returns nil.
+
+
+
+An optional numeric argument init makes the search
+starts at that position in the subject string.
+As usual in Lua libraries,
+a negative value counts from the end.
+
+
+
re.gsub (subject, pattern, replacement)
+
+Does a global substitution,
+replacing all occurrences of pattern
+in the given subject by replacement.
+
+
re.match (subject, pattern)
+
+Matches the given pattern against the given subject,
+returning all captures.
+
+
+
re.updatelocale ()
+
+Updates the pre-defined character classes to the current locale.
+
+The next code shows a simple complete Lua program using
+the re module:
+
+
+local re = require"re"
+
+-- find the position of the first numeral in a string
+print(re.find("the number 423 is odd", "[0-9]+")) --> 12 14
+
+-- returns all words in a string
+print(re.match("the number 423 is odd", "({%a+} / .)*"))
+--> the number is odd
+
+-- returns the first numeral in a string
+print(re.match("the number 423 is odd", "s <- {%d+} / . s"))
+--> 423
+
+print(re.gsub("hello World", "[aeiou]", "."))
+--> h.ll. W.rld
+
+
+
+
Balanced parentheses
+
+The following call will produce the same pattern produced by the
+Lua expression in the
+balanced parentheses example:
+
+This example shows a simple way to build an
+abstract syntax tree (AST) for a given grammar.
+To keep our example simple,
+let us consider the following grammar
+for lists of names:
+
+
+p = re.compile[[
+ listname <- (name s)*
+ name <- [a-z][a-z]*
+ s <- %s*
+]]
+
+
+Now, we will add captures to build a corresponding AST.
+As a first step, the pattern will build a table to
+represent each non terminal;
+terminals will be represented by their corresponding strings:
+
+Now, a match against "hi hello bye"
+results in the table
+{{"hi"}, {"hello"}, {"bye"}}.
+
+
+For such a simple grammar,
+this AST is more than enough;
+actually, the tables around each single name
+are already overkilling.
+More complex grammars,
+however, may need some more structure.
+Specifically,
+it would be useful if each table had
+a tag field telling what non terminal
+that table represents.
+We can add such a tag using
+named group captures:
+
+A text is a sequence of items,
+wherein we apply a substitution capture to expand any macros.
+An item is either a macro,
+any character different from parentheses,
+or a parenthesized expression.
+A macro argument (arg) is a sequence
+of items different from a comma.
+(Note that a comma may appear inside an item,
+e.g., inside a parenthesized expression.)
+Again we do a substitution capture to expand any macro
+in the argument before expanding the outer macro.
+args is a list of arguments separated by commas.
+Finally we define the macros.
+Each macro is a string substitution;
+it replaces the macro name and its arguments by its corresponding string,
+with each %n replaced by the n-th argument.
+
+
+
Patterns
+
+This example shows the complete syntax
+of patterns accepted by re.
+
+
+p = [=[
+
+pattern <- exp !.
+exp <- S (grammar / alternative)
+
+alternative <- seq ('/' S seq)*
+seq <- prefix*
+prefix <- '&' S prefix / '!' S prefix / suffix
+suffix <- primary S (([+*?]
+ / '^' [+-]? num
+ / '->' S (string / '{}' / name)
+ / '=>' S name) S)*
+
+primary <- '(' exp ')' / string / class / defined
+ / '{:' (name ':')? exp ':}'
+ / '=' name
+ / '{}'
+ / '{~' exp '~}'
+ / '{' exp '}'
+ / '.'
+ / name S !arrow
+ / '<' name '>' -- old-style non terminals
+
+grammar <- definition+
+definition <- name S arrow exp
+
+class <- '[' '^'? item (!']' item)* ']'
+item <- defined / range / .
+range <- . '-' [^]]
+
+S <- (%s / '--' [^%nl]*)* -- spaces and comments
+name <- [A-Za-z][A-Za-z0-9_]*
+arrow <- '<-'
+num <- [0-9]+
+string <- '"' [^"]* '"' / "'" [^']* "'"
+defined <- '%' name
+
+]=]
+
+print(re.match(p, p)) -- a self description must match itself
+
+Permission is hereby granted, free of charge,
+to any person obtaining a copy of this software and
+associated documentation files (the "Software"),
+to deal in the Software without restriction,
+including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software,
+and to permit persons to whom the Software is
+furnished to do so,
+subject to the following conditions:
+
+
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of the Software.
+
+
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+
+
+
+
+
+
+
+
diff --git a/luaclib/src/lpeg/re.lua b/luaclib/src/lpeg/re.lua
new file mode 100644
index 00000000..40cdbf09
--- /dev/null
+++ b/luaclib/src/lpeg/re.lua
@@ -0,0 +1,265 @@
+-- $Id: re.lua $
+
+-- imported functions and modules
+local tonumber, type, print, error = tonumber, type, print, error
+local setmetatable = setmetatable
+local m = require"lpeg"
+
+-- 'm' will be used to parse expressions, and 'mm' will be used to
+-- create expressions; that is, 're' runs on 'm', creating patterns
+-- on 'mm'
+local mm = m
+
+-- pattern's metatable
+local mt = getmetatable(mm.P(0))
+
+
+
+-- No more global accesses after this point
+local version = _VERSION
+if version == "Lua 5.2" then _ENV = nil end
+
+
+local any = m.P(1)
+
+
+-- Pre-defined names
+local Predef = { nl = m.P"\n" }
+
+
+local mem
+local fmem
+local gmem
+
+
+local function updatelocale ()
+ mm.locale(Predef)
+ Predef.a = Predef.alpha
+ Predef.c = Predef.cntrl
+ Predef.d = Predef.digit
+ Predef.g = Predef.graph
+ Predef.l = Predef.lower
+ Predef.p = Predef.punct
+ Predef.s = Predef.space
+ Predef.u = Predef.upper
+ Predef.w = Predef.alnum
+ Predef.x = Predef.xdigit
+ Predef.A = any - Predef.a
+ Predef.C = any - Predef.c
+ Predef.D = any - Predef.d
+ Predef.G = any - Predef.g
+ Predef.L = any - Predef.l
+ Predef.P = any - Predef.p
+ Predef.S = any - Predef.s
+ Predef.U = any - Predef.u
+ Predef.W = any - Predef.w
+ Predef.X = any - Predef.x
+ mem = {} -- restart memoization
+ fmem = {}
+ gmem = {}
+ local mt = {__mode = "v"}
+ setmetatable(mem, mt)
+ setmetatable(fmem, mt)
+ setmetatable(gmem, mt)
+end
+
+
+updatelocale()
+
+
+
+local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end)
+
+
+local function patt_error (s, i)
+ local msg = (#s < i + 20) and s:sub(i)
+ or s:sub(i,i+20) .. "..."
+ msg = ("pattern error near '%s'"):format(msg)
+ error(msg, 2)
+end
+
+local function mult (p, n)
+ local np = mm.P(true)
+ while n >= 1 do
+ if n%2 >= 1 then np = np * p end
+ p = p * p
+ n = n/2
+ end
+ return np
+end
+
+local function equalcap (s, i, c)
+ if type(c) ~= "string" then return nil end
+ local e = #c + i
+ if s:sub(i, e - 1) == c then return e else return nil end
+end
+
+
+local S = (Predef.space + "--" * (any - Predef.nl)^0)^0
+
+local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0
+
+local arrow = S * "<-"
+
+local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1
+
+name = m.C(name)
+
+
+-- a defined name only have meaning in a given environment
+local Def = name * m.Carg(1)
+
+
+local function getdef (id, defs)
+ local c = defs and defs[id]
+ if not c then error("undefined name: " .. id) end
+ return c
+end
+
+-- match a name and return a group of its corresponding definition
+-- and 'f' (to be folded in 'Suffix')
+local function defwithfunc (f)
+ return m.Cg(Def / getdef * m.Cc(f))
+end
+
+
+local num = m.C(m.R"09"^1) * S / tonumber
+
+local String = "'" * m.C((any - "'")^0) * "'" +
+ '"' * m.C((any - '"')^0) * '"'
+
+
+local defined = "%" * Def / function (c,Defs)
+ local cat = Defs and Defs[c] or Predef[c]
+ if not cat then error ("name '" .. c .. "' undefined") end
+ return cat
+end
+
+local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R
+
+local item = (defined + Range + m.C(any)) / m.P
+
+local Class =
+ "["
+ * (m.C(m.P"^"^-1)) -- optional complement symbol
+ * m.Cf(item * (item - "]")^0, mt.__add) /
+ function (c, p) return c == "^" and any - p or p end
+ * "]"
+
+local function adddef (t, k, exp)
+ if t[k] then
+ error("'"..k.."' already defined as a rule")
+ else
+ t[k] = exp
+ end
+ return t
+end
+
+local function firstdef (n, r) return adddef({n}, n, r) end
+
+
+local function NT (n, b)
+ if not b then
+ error("rule '"..n.."' used outside a grammar")
+ else return mm.V(n)
+ end
+end
+
+
+local exp = m.P{ "Exp",
+ Exp = S * ( m.V"Grammar"
+ + m.Cf(m.V"Seq" * ("/" * S * m.V"Seq")^0, mt.__add) );
+ Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix"^0 , mt.__mul)
+ * (#seq_follow + patt_error);
+ Prefix = "&" * S * m.V"Prefix" / mt.__len
+ + "!" * S * m.V"Prefix" / mt.__unm
+ + m.V"Suffix";
+ Suffix = m.Cf(m.V"Primary" * S *
+ ( ( m.P"+" * m.Cc(1, mt.__pow)
+ + m.P"*" * m.Cc(0, mt.__pow)
+ + m.P"?" * m.Cc(-1, mt.__pow)
+ + "^" * ( m.Cg(num * m.Cc(mult))
+ + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow))
+ )
+ + "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div))
+ + m.P"{}" * m.Cc(nil, m.Ct)
+ + defwithfunc(mt.__div)
+ )
+ + "=>" * S * defwithfunc(m.Cmt)
+ + "~>" * S * defwithfunc(m.Cf)
+ ) * S
+ )^0, function (a,b,f) return f(a,b) end );
+ Primary = "(" * m.V"Exp" * ")"
+ + String / mm.P
+ + Class
+ + defined
+ + "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" /
+ function (n, p) return mm.Cg(p, n) end
+ + "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end
+ + m.P"{}" / mm.Cp
+ + "{~" * m.V"Exp" * "~}" / mm.Cs
+ + "{|" * m.V"Exp" * "|}" / mm.Ct
+ + "{" * m.V"Exp" * "}" / mm.C
+ + m.P"." * m.Cc(any)
+ + (name * -arrow + "<" * name * ">") * m.Cb("G") / NT;
+ Definition = name * arrow * m.V"Exp";
+ Grammar = m.Cg(m.Cc(true), "G") *
+ m.Cf(m.V"Definition" / firstdef * m.Cg(m.V"Definition")^0,
+ adddef) / mm.P
+}
+
+local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error)
+
+
+local function compile (p, defs)
+ if mm.type(p) == "pattern" then return p end -- already compiled
+ local cp = pattern:match(p, 1, defs)
+ if not cp then error("incorrect pattern", 3) end
+ return cp
+end
+
+local function match (s, p, i)
+ local cp = mem[p]
+ if not cp then
+ cp = compile(p)
+ mem[p] = cp
+ end
+ return cp:match(s, i or 1)
+end
+
+local function find (s, p, i)
+ local cp = fmem[p]
+ if not cp then
+ cp = compile(p) / 0
+ cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) }
+ fmem[p] = cp
+ end
+ local i, e = cp:match(s, i or 1)
+ if i then return i, e - 1
+ else return i
+ end
+end
+
+local function gsub (s, p, rep)
+ local g = gmem[p] or {} -- ensure gmem[p] is not collected while here
+ gmem[p] = g
+ local cp = g[rep]
+ if not cp then
+ cp = compile(p)
+ cp = mm.Cs((cp / rep + 1)^0)
+ g[rep] = cp
+ end
+ return cp:match(s)
+end
+
+
+-- exported names
+local re = {
+ compile = compile,
+ match = match,
+ find = find,
+ gsub = gsub,
+ updatelocale = updatelocale,
+}
+
+return re
diff --git a/luaclib/src/lsys.c b/luaclib/src/lsys.c
index 133f7c1c..276a0ba8 100644
--- a/luaclib/src/lsys.c
+++ b/luaclib/src/lsys.c
@@ -1,54 +1,201 @@
#define LUA_LIB
-#include "../../src/core.h"
+#include
+#include
+
+#define MAX_IPV4 (4294967295L)
+
+// 提供一个精确到微秒的时间戳
+static int lnow(lua_State *L){
+ lua_pushnumber(L, now());
+ return 1;
+}
// 提供一个精确到毫秒的时间戳
-static int
-lnow(lua_State *L){
- lua_pushnumber(L, now());
- return 1;
-}
-
-int /* 此方法可用于检查是否为有效ipv4地址*/
-lipv4(lua_State *L){
- const char *IP = lua_tostring(L, 1);
- if (!IP) return luaL_error(L, "ipv4 error: 请至少传递一个string类型参数\n");
- if (ipv4(IP)) lua_pushboolean(L, 1);
- else lua_pushboolean(L, 0);
- return 1;
-}
-
-int /* 此方法可用于检查是否为有效ipv6地址*/
-lipv6(lua_State *L){
- const char *IP = lua_tostring(L, 1);
- if (!IP) return luaL_error(L, "ipv6 error: 请至少传递一个string类型参数\n");
- if (ipv6(IP)) lua_pushboolean(L, 1);
- else lua_pushboolean(L, 0);
- return 1;
-}
-
-int
-ldate(lua_State *L){
- const char *fmt = lua_tostring(L, 1);
- if (!fmt) return luaL_error(L, "Date: 错误的格式化方法");
- time_t timestamp = lua_tointeger(L, 2);
- char fmttime[64];
- time_t t = time(×tamp);
- strftime(fmttime, 64, fmt, localtime(&t));
- lua_pushstring(L, fmttime);
- return 1;
-}
-
-LUAMOD_API int
-luaopen_sys(lua_State *L){
- luaL_checkversion(L);
- luaL_Reg sys_libs[] = {
- {"now", lnow},
- {"ipv4", lipv4},
- {"ipv6", lipv6},
- {"date", ldate},
- {NULL, NULL}
- };
- luaL_newlib(L, sys_libs);
- return 1;
-}
\ No newline at end of file
+static int ltime(lua_State *L){
+ lua_pushinteger(L, (uint64_t)(now() * 1e3));
+ return 1;
+}
+
+/* 此方法可用于检查是否为有效ipv4地址*/
+static int lipv4(lua_State *L){
+ size_t str_len = 0;
+ const char *IP = luaL_checklstring(L, 1, &str_len);
+ if (!IP || str_len == 0)
+ return luaL_error(L, "ipv4 error: A parameter of type string is required\n");
+ lua_pushboolean(L, ipv4(IP));
+ return 1;
+}
+
+/* 此方法可用于检查是否为有效ipv6地址*/
+static int lipv6(lua_State *L){
+ size_t str_len = 0;
+ const char *IP = luaL_checklstring(L, 1, &str_len);
+ if (!IP || str_len == 0)
+ return luaL_error(L, "ipv6 error: A parameter of type string is required\n");
+ lua_pushboolean(L, ipv6(IP));
+ return 1;
+}
+
+/* string 转换为 IPv4 */
+static int lstr2ip(lua_State *L){
+ size_t str_len = 0;
+ const char *IP = luaL_checklstring(L, 1, &str_len);
+ if (!IP || str_len < 3 || str_len > 15)
+ return luaL_error(L, "Invalid IP.");
+ uint32_t addr = 0;
+ if (inet_pton(AF_INET, IP, (void*)&addr) != 1)
+ return 0;
+ lua_pushinteger(L, addr);
+ return 1;
+}
+
+/* IPv4 转换为 string */
+static int lip2str(lua_State *L){
+ lua_Unsigned IP = luaL_checkinteger(L, 1);
+ if (IP > MAX_IPV4)
+ return luaL_error(L, "Invalid IP.");
+ char str[INET_ADDRSTRLEN];
+ memset(str, 0x0, INET_ADDRSTRLEN);
+ if (!inet_ntop(AF_INET, (const void*)&IP, str, INET_ADDRSTRLEN))
+ return 0;
+ lua_pushlstring(L, str, strlen(str));
+ return 1;
+}
+
+/* 返回格式化后的时间 */
+static int ldate(lua_State *L){
+ size_t str_len = 0;
+ const char *fmt = luaL_checklstring(L, 1, &str_len);
+ if (!fmt || str_len == 0)
+ return luaL_error(L, "Date: Invalid format.");
+
+ time_t timestamp = lua_tointeger(L, 2);
+ if (0 >= timestamp)
+ timestamp = time(NULL);
+
+ size_t len = 128 + str_len;
+ char fmttime[len];
+ memset(fmttime, 0x0, len);
+ int result = strftime(fmttime, len, fmt, localtime(×tamp));
+ if (result < 0)
+ return 0;
+ lua_pushlstring(L, fmttime, result);
+ return 1;
+}
+
+/* 返回当前操作系统类型 */
+static int los(lua_State *L){
+ lua_pushstring(L, os());
+ return 1;
+}
+
+/* 返回主机名 */
+static int lhostname(lua_State *L){
+ size_t max_hostaname = 4096;
+ char *hostname = lua_newuserdata(L, max_hostaname);
+ memset(hostname, 0x0, max_hostaname);
+ int len = gethostname(hostname, max_hostaname);
+ if (0 > len)
+ return 0;
+ lua_pushlstring(L, hostname, strlen(hostname));
+ return 1;
+}
+
+/* 创建表 */
+static int lnew_tab(lua_State *L){
+ // lua_Integer array_size = luaL_checkinteger(L, 1); // array 部分大小
+ // lua_Integer hash_size = luaL_checkinteger(L, 2); // hash 部分大小
+ lua_createtable(L, luaL_checkinteger(L, 1), luaL_checkinteger(L, 2));
+ return 1;
+}
+
+/* 创建表 */
+static int lusage(lua_State *L) {
+ #include
+ struct rusage usage;
+ int ret = getrusage(RUSAGE_SELF , &usage);
+ if (ret == -1){
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ return 2;
+ }
+
+ lua_createtable(L, 0, 8);
+
+ #define luaL_setki(L, k, i) ({lua_pushstring(L, (k)); lua_pushinteger(L, (i)); lua_rawset(L, -3);})
+ #define luaL_setkn(L, k, v) ({lua_pushstring(L, (k)); lua_pushnumber(L, (v)); lua_rawset(L, -3);})
+ luaL_setkn(L, "utime", usage.ru_utime.tv_sec + (usage.ru_utime.tv_usec * 1e-6));
+ luaL_setkn(L, "ktime", usage.ru_stime.tv_sec + (usage.ru_stime.tv_usec * 1e-6));
+ luaL_setki(L, "rss", usage.ru_maxrss); luaL_setki(L, "swap", usage.ru_nswap);
+ luaL_setki(L, "inblock", usage.ru_inblock); luaL_setki(L, "oublock", usage.ru_oublock);
+ luaL_setki(L, "hard_page_fault", usage.ru_majflt); luaL_setki(L, "soft_page_fault", usage.ru_minflt);
+ #undef luaL_setki
+ #undef luaL_setkn
+
+ return 1;
+}
+
+static int linterface(lua_State *L){
+ struct ifaddrs *ifc, *ifc1;
+ if(getifaddrs(&ifc))
+ return 0;
+ lua_createtable(L, 32, 0);
+ ifc1 = ifc;
+ int index = 1;
+ for(; NULL != ifc; ifc = (*ifc).ifa_next) {
+ if ((*ifc).ifa_addr && (*ifc).ifa_netmask && (*ifc).ifa_name) {
+ char ip[64] = {0};
+ char mask[64] = {0};
+ // IPv4
+ if ((*ifc).ifa_addr->sa_family == AF_INET && (*ifc).ifa_netmask->sa_family == AF_INET) {
+ inet_ntop(AF_INET, &(((struct sockaddr_in*)((*ifc).ifa_addr))->sin_addr), ip, 64);
+ inet_ntop(AF_INET, &(((struct sockaddr_in*)((*ifc).ifa_netmask))->sin_addr), mask, 64);
+ if (0 != strncmp("0.0.0.0", ip, strlen(ip)) && 0 != strncmp("0.0.0.0", mask, strlen(mask))){
+ lua_createtable(L, 0, 4);
+ lua_pushliteral(L, "Interface"); lua_pushstring(L, (*ifc).ifa_name); lua_rawset(L, -3);
+ lua_pushliteral(L, "IP"); lua_pushstring(L, ip); lua_rawset(L, -3);
+ lua_pushliteral(L, "Mask"); lua_pushstring(L, mask); lua_rawset(L, -3);
+ lua_pushliteral(L, "Version"); lua_pushliteral(L, "IPv4"); lua_rawset(L, -3);
+ lua_rawseti(L, -2, index++);
+ }
+ }
+ // IPv6
+ if ((*ifc).ifa_addr->sa_family == AF_INET6 && (*ifc).ifa_netmask->sa_family == AF_INET6) {
+ inet_ntop(AF_INET6, &(((struct sockaddr_in6*)((*ifc).ifa_addr))->sin6_addr), ip, 64);
+ inet_ntop(AF_INET6, &(((struct sockaddr_in6*)((*ifc).ifa_netmask))->sin6_addr), mask, 64);
+ if (0 != strncmp("::", ip, strlen(ip)) && 0 != strncmp(ip, "fe80", 4)) {
+ lua_createtable(L, 0, 4);
+ lua_pushliteral(L, "Interface"); lua_pushstring(L, (*ifc).ifa_name); lua_rawset(L, -3);
+ lua_pushliteral(L, "IP"); lua_pushstring(L, ip); lua_rawset(L, -3);
+ lua_pushliteral(L, "Mask"); lua_pushstring(L, mask); lua_rawset(L, -3);
+ lua_pushliteral(L, "Version"); lua_pushliteral(L, "IPv6"); lua_rawset(L, -3);
+ lua_rawseti(L, -2, index++);
+ }
+ }
+ }
+ }
+ freeifaddrs(ifc1);
+ return 1;
+}
+
+LUAMOD_API int luaopen_sys(lua_State *L){
+ luaL_checkversion(L);
+ luaL_Reg sys_libs[] = {
+ {"os", los},
+ {"now", lnow},
+ {"time", ltime},
+ {"date", ldate},
+ {"ipv4", lipv4},
+ {"ipv6", lipv6},
+ {"usage", lusage},
+ {"str2ip", lstr2ip},
+ {"ip2str", lip2str},
+ {"hostname", lhostname},
+ {"interface", linterface},
+ {"new_tab", lnew_tab},
+ {NULL, NULL}
+ };
+ luaL_newlib(L, sys_libs);
+ return 1;
+}
diff --git a/luaclib/src/ltask.c b/luaclib/src/ltask.c
index 172fde9f..3133926f 100644
--- a/luaclib/src/ltask.c
+++ b/luaclib/src/ltask.c
@@ -1,12 +1,11 @@
#define LUA_LIB
-#include "../../src/core.h"
+#include
-static void
-TASK_CB(CORE_P_ core_task *task, int revents){
+static void TASK_CB(CORE_P_ core_task *task, int revents){
lua_State *co = (lua_State *) core_get_watcher_userdata(task);
if (co && (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK)){
- int status = lua_resume(co, NULL, lua_status(co) == LUA_YIELD ? lua_gettop(co) : lua_gettop(co) - 1);
+ int status = CO_RESUME(co, NULL, lua_status(co) == LUA_YIELD ? lua_gettop(co) : lua_gettop(co) - 1);
if (status != LUA_YIELD && status != LUA_OK){
LOG("ERROR", lua_tostring(co, -1));
}
@@ -14,60 +13,47 @@ TASK_CB(CORE_P_ core_task *task, int revents){
}
}
-int
-task_new(lua_State *L){
+static int task_new(lua_State *L){
core_task *task = lua_newuserdata(L, sizeof(core_task));
- if (!task) return 0;
-
+ if (!task)
+ return 0;
core_task_init(task, TASK_CB);
-
luaL_setmetatable(L, "__Task__");
-
return 1;
}
-int
-task_start(lua_State *L){
+static int task_start(lua_State *L){
core_task *task = (core_task *) luaL_testudata(L, 1, "__Task__");
- if (!task) return luaL_error(L, "attemp to pass a invaild core_task value.");
-
+ if (!task)
+ return luaL_error(L, "attemp to pass a invaild core_task value.");
lua_State *co = lua_tothread(L, 2);
- if (!co) return luaL_error(L, "attemp to pass a invaild lua_State value.");
-
+ if (!co)
+ return luaL_error(L, "attemp to pass a invaild lua_State value.");
/* 这里假设栈大小永远够用, 因为调用与回调都不需要传入那么多参数 */
lua_xmove(L, co, lua_gettop(L) - 2);
-
core_set_watcher_userdata(task, co);
-
core_task_start(CORE_LOOP_ task);
-
return 1;
}
-int
-task_stop(lua_State *L){
+static int task_stop(lua_State *L){
core_task *task = (core_task *) luaL_testudata(L, 1, "__Task__");
- if (!task) return luaL_error(L, "attemp to pass a invaild core_task value.");
-
+ if (!task)
+ return luaL_error(L, "attemp to pass a invaild core_task value.");
core_task_stop(CORE_LOOP_ task);
-
return 0;
}
-LUAMOD_API int
-luaopen_task(lua_State *L){
-
+LUAMOD_API int luaopen_task(lua_State *L){
luaL_checkversion(L);
-
luaL_newmetatable(L, "__Task__");
lua_pushstring (L, "__index");
lua_pushvalue(L, -2);
lua_rawset(L, -3);
- lua_pushliteral(L, "__mode");
- lua_pushliteral(L, "kv");
- lua_rawset(L, -3);
-
+ lua_pushliteral(L, "__mode");
+ lua_pushliteral(L, "kv");
+ lua_rawset(L, -3);
luaL_Reg task_libs[] = {
{"new", task_new},
{"start", task_start},
diff --git a/luaclib/src/ltcp.c b/luaclib/src/ltcp.c
index b376dcc7..71bcd779 100644
--- a/luaclib/src/ltcp.c
+++ b/luaclib/src/ltcp.c
@@ -1,506 +1,1267 @@
#define LUA_LIB
+#include
#include
#include
#include
-#include "../../src/core.h"
-static inline
-void SETSOCKETOPT(int sockfd){
- /* 设置非阻塞 */
- non_blocking(sockfd);
-
- int ENABLE = 1;
-
- /* 地址/端口重用 */
- setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &ENABLE, sizeof(ENABLE));
-
- /* 关闭小包延迟合并算法 */
- setsockopt(sockfd, SOL_SOCKET, TCP_NODELAY, &ENABLE, sizeof(ENABLE));
+#ifndef alloca
+ #define alloca __alloca
+#endif
+
+#ifndef MSG_NOSIGNAL
+ #define MSG_NOSIGNAL (0)
+#endif
+
+#define MBSIZE (262144)
+
+#define None (-1)
+#define SERVER (0)
+#define CLIENT (1)
+
+static inline void SETSOCKETOPT(int sockfd, int mode){
+ int Enable = 1;
+ int ret = 0;
+ /* 设置非阻塞 */
+ non_blocking(sockfd);
+/* 地址重用 */
+#ifdef SO_REUSEADDR
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &Enable, sizeof(Enable));
+ if (ret < 0) {
+ LOG("ERROR", "Setting SO_REUSEADDR failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+#endif
+
+/* 端口重用 */
+#ifdef SO_REUSEPORT
+ if (mode == SERVER) {
+ #ifdef SO_REUSEPORT_LB
+ // BSD系统的多进程负载需要使用此宏
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT_LB, &Enable, sizeof(Enable));
+ #else
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &Enable, sizeof(Enable));
+ #endif
+ if (ret < 0) {
+ LOG("ERROR", "Setting SO_REUSEPORT failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+ }
+#endif
+
+#ifdef SO_NOSIGPIPE
+ // 屏蔽信号
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &Enable, sizeof(Enable));
+ if (ret < 0) {
+ LOG("ERROR", "Setting SO_NOSIGPIPE failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+#endif
+
+/* 关闭连接不会阻塞 */
+#ifdef SO_LINGER
+ struct linger lin = { .l_onoff = 0, .l_linger = 0 };
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &lin , sizeof(lin));
+ if (ret < 0){
+ LOG("ERROR", "Setting SO_LINGER failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+#endif
+
+/* 开启 TCP keepalive */
+#ifdef SO_KEEPALIVE
+ if (mode != None){
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &Enable , sizeof(Enable));
+ if (ret < 0){
+ LOG("ERROR", "Setting SO_KEEPALIVE failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+ }
+#endif
+
+/* 关闭小包延迟合并算法 */
+#ifdef TCP_NODELAY
+ ret = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &Enable, sizeof(Enable));
+ if (ret < 0){
+ LOG("ERROR", "Setting TCP_NODELAY failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+#endif
+
+/* 开启延迟Accept, 没数据来之前不回调accept */
+#if defined(TCP_DEFER_ACCEPT)
+ if (mode == SERVER) {
+ ret = setsockopt(sockfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &Enable, sizeof(Enable));
+ if (ret < 0){
+ LOG("WARN", "Setting TCP_DEFER_ACCEPT failed.");
+ LOG("WARN", strerror(errno));
+ // 不能退出的原因是因为要兼容那垃圾WSL实现.
+ // return core_exit();
+ }
+ }
+#elif defined(SO_ACCEPTFILTER)
+ /* TODO: 暂不实现 */
+#endif
+
+/* 开启TCP快速连接 */
+#if defined(TCP_FASTOPEN) && defined(MSG_FASTOPEN)
+ if (mode == SERVER) {
+ int len = 5;
+ ret = setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN, &len, sizeof(len));
+ if (ret < 0){
+ LOG("WARN", "Setting TCP_FASTOPEN failed.");
+ LOG("WARN", strerror(errno));
+ // 不能退出的原因是因为要兼容那垃圾WSL实现.
+ // return core_exit();
+ }
+ }
+#endif
+
+/* 设置 TCP keepalive 空闲时间 */
+#ifdef TCP_KEEPIDLE
+ int keepidle = 30;
+ ret = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle , sizeof(keepidle));
+ if (ret < 0){
+ LOG("ERROR", "Setting TCP_KEEPIDLE failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+#endif
+
+/* 设置 TCP keepalive 探测总次数 */
+#ifdef TCP_KEEPCNT
+ int keepcount = 3;
+ ret = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &keepcount , sizeof(keepcount));
+ if (ret < 0){
+ LOG("ERROR", "Setting TCP_KEEPCNT failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+#endif
+
+/* 设置 TCP keepalive 每次探测间隔时间 */
+#ifdef TCP_KEEPINTVL
+ int keepinterval = 5;
+ ret = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &keepinterval , sizeof(keepinterval));
+ if (ret < 0){
+ LOG("ERROR", "Setting TCP_KEEPINTVL failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+#endif
+
+/* 开启IPV6与ipv4双栈 */
+#ifdef IPV6_V6ONLY
+ if (mode != None) {
+ int No = 0;
+ ret = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&No, sizeof(No));
+ if (ret < 0){
+ LOG("ERROR", "Setting IPV6_V6ONLY failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+ }
+#endif
}
/* server fd */
-static int
-create_server_fd(int port, int backlog){
- errno = 0;
-
- int sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
- if (0 >= sockfd) return -1;
-
- SETSOCKETOPT(sockfd);
-
- struct sockaddr_in6 SA;
- SA.sin6_family = AF_INET6;
- SA.sin6_port = htons(port);
- SA.sin6_addr = in6addr_any;
-
- int bind_siccess = bind(sockfd, (struct sockaddr *)&SA, sizeof(struct sockaddr_in6));
- if (0 > bind_siccess) {
- return -1; /* 绑定套接字失败 */
- }
-
- int listen_success = listen(sockfd, backlog);
- if (0 > listen_success) {
- return -1; /* 监听套接字失败 */
- }
- return sockfd;
+static int create_server_fd(const char *ip, int port, int backlog){
+ errno = 0;
+ /* 建立 TCP Server Socket */
+ int sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
+ if (0 >= sockfd){
+ LOG("ERROR", strerror(errno));
+ return -1;
+ }
+ /* socket option set */
+ SETSOCKETOPT(sockfd, SERVER);
+
+ struct sockaddr_in6 SA;
+ memset(&SA, 0x0, sizeof(SA));
+
+ SA.sin6_family = AF_INET6;
+ SA.sin6_port = htons(port);
+ SA.sin6_addr = in6addr_any;
+
+ struct in6_addr addr;
+ /* 如果填写的是`::1` */
+ if (!strcmp(ip, "::1") && inet_pton(AF_INET6, "::1", &addr) == 1){
+ SA.sin6_addr = addr;
+ /* 如果填写的是`127.0.0.1` */
+ } else if (!strcmp(ip, "127.0.0.1") && inet_pton(AF_INET6, "::ffff:127.0.0.1", &addr) == 1) {
+ SA.sin6_addr = addr;
+ /* 如果填写的是其它`IPv6`地址 */
+ } else if (inet_pton(AF_INET6, ip, &addr) == 1) {
+ SA.sin6_addr = addr;
+ /* 如果填写的是 `0.0.0.0` 则监听所有地址 */
+ } else if (!strcmp(ip, "0.0.0.0")) {
+ SA.sin6_addr = in6addr_any;
+ /* 检查IPv4地址或者是非法IP地址 */
+ } else {
+ struct in_addr addr4;
+ if (inet_pton(AF_INET, ip, &addr4) == 1) {
+ char *ipv6 = alloca(strlen(ip) + 8);
+ memset(ipv6, 0x0, strlen(ip) + 8);
+ memmove(ipv6, "::ffff:", 7);
+ memmove(ipv6 + 7, ip, strlen(ip));
+ if (inet_pton(AF_INET6, ipv6, &addr) != 1){
+ close(sockfd);
+ return -1;
+ }
+ SA.sin6_addr = addr;
+ }
+ }
+
+ /* 绑定套接字失败 */
+ int bind_success = bind(sockfd, (struct sockaddr *)&SA, sizeof(SA));
+ if (0 > bind_success) {
+ close(sockfd);
+ return -1;
+ }
+
+ /* 监听套接字失败 */
+ int listen_success = listen(sockfd, backlog);
+ if (0 > listen_success) {
+ close(sockfd);
+ return -1;
+ }
+
+ return sockfd;
}
/* client fd */
-static int
-create_client_fd(const char *ipaddr, int port){
- errno = 0;
-
- /* 建立socket */
- int sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
- if (0 >= sockfd) return -1;
-
- SETSOCKETOPT(sockfd);
+static int create_client_fd(const char *ipaddr, int port){
+ errno = 0;
+ /* 建立 TCP Client Socket */
+ int sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
+ if (0 >= sockfd) {
+ LOG("ERROR", strerror(errno));
+ return -1;
+ }
+
+ /* socket option set */
+ SETSOCKETOPT(sockfd, CLIENT);
+
+ struct sockaddr_in6 SA;
+ memset(&SA, 0x0, sizeof(SA));
+
+ SA.sin6_family = AF_INET6;
+ SA.sin6_port = htons(port);
+ int error = inet_pton(AF_INET6, ipaddr, &SA.sin6_addr);
+ if (1 != error) {
+ LOG("ERROR", strerror(errno));
+ close(sockfd);
+ return -1;
+ }
+
+ int ret = connect(sockfd, (struct sockaddr*)&SA, sizeof(SA));
+ if (ret != 0 && errno != EINPROGRESS) {
+ close(sockfd);
+ return -1;
+ }
+ return sockfd;
+}
- struct sockaddr_in6 SA;
- SA.sin6_family = AF_INET6;
- SA.sin6_port = htons(port);
- inet_pton(AF_INET6, ipaddr, &SA.sin6_addr);
- connect(sockfd, (struct sockaddr*)&SA, sizeof(SA));
- if (errno != EINPROGRESS){
- close(sockfd);
- return -1;
- }
- return sockfd;
+static int create_server_unixsock(const char* path, size_t path_len, int backlog) {
+ errno = 0;
+ int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (0 >= sockfd){
+ LOG("ERROR", strerror(errno));
+ return -1;
+ }
+
+ struct sockaddr_un UN;
+ memset(&UN, 0x0, sizeof(UN));
+
+ UN.sun_family = AF_LOCAL;
+ memmove(UN.sun_path, path, path_len);
+
+ non_blocking(sockfd);
+
+ /* 绑定套接字失败 */
+ int bind_success = bind(sockfd, (struct sockaddr *)&UN, sizeof(UN));
+ if (0 > bind_success) {
+ LOG("ERROR", strerror(errno));
+ close(sockfd);
+ return -1;
+ }
+
+ /* 监听套接字失败 */
+ int listen_success = listen(sockfd, backlog);
+ if (0 > listen_success) {
+ LOG("ERROR", strerror(errno));
+ close(sockfd);
+ return -1;
+ }
+
+ return sockfd;
}
+static int create_client_unixsock(const char* path, size_t path_len) {
+ errno = 0;
+ int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (0 >= sockfd){
+ LOG("ERROR", strerror(errno));
+ return -1;
+ }
+ struct sockaddr_un UN;
+ memset(&UN, 0x0, sizeof(UN));
-static void
-TCP_IO_CB(CORE_P_ core_io *io, int revents) {
+ UN.sun_family = AF_LOCAL;
+ memmove(UN.sun_path, path, path_len);
- int status = 0;
+ non_blocking(sockfd);
- if (revents & EV_ERROR) {
- LOG("ERROR", "Recevied a core_io object internal error from libev.");
- return ;
- }
+ int ret = connect(sockfd, (struct sockaddr*)&UN, sizeof(UN));
+ if (0 > ret) {
+ // LOG("ERROR", strerror(errno));
+ close(sockfd);
+ return -1;
+ }
- lua_State *co = (lua_State *)core_get_watcher_userdata(io);
- if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){
- status = lua_resume(co, NULL, 0);
- if (status != LUA_YIELD && status != LUA_OK){
- LOG("ERROR", lua_tostring(co, -1));
- core_io_stop(CORE_LOOP_ io);
- }
- }
+ return sockfd;
}
-static void
-IO_CONNECT(CORE_P_ core_io *io, int revents){
-
- if (revents & EV_ERROR) {
- LOG("ERROR", "Recevied a core_io object internal error from libev.");
- return ;
- }
-
- if (revents & EV_WRITE){
- lua_State *co = (lua_State *)core_get_watcher_userdata(io);
- if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){
- socklen_t len;
- int status = 0, CONNECTED = 0, err = 0;
- if(getsockopt(io->fd, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) CONNECTED = 1;
- lua_pushboolean(co, CONNECTED);
- status = lua_resume(co, NULL, 1);
- if (status != LUA_YIELD && status != LUA_OK){
- LOG("ERROR", lua_tostring(co, -1));
- core_io_stop(CORE_LOOP_ io);
- }
- }
- }
-
-}
-
-static void /* 接受链接 */
-IO_ACCEPT(CORE_P_ core_io *io, int revents){
-
- if (revents & EV_READ){
- for(;;) {
- errno = 0;
- struct sockaddr_in6 SA;
- socklen_t slen = sizeof(struct sockaddr_in6);
- int client = accept(io->fd, (struct sockaddr*)&SA, &slen);
- if (0 >= client) {
- if (errno != EAGAIN)
- LOG("INFO", strerror(errno));
- return ;
- }
- lua_State *co = (lua_State *) core_get_watcher_userdata(io);
- if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){
- char buf[INET6_ADDRSTRLEN];
- inet_ntop(AF_INET6, &SA.sin6_addr, buf, INET6_ADDRSTRLEN);
- lua_pushinteger(co, client);
- lua_pushlstring(co, buf, strlen(buf));
- int status = lua_resume(co, NULL, lua_status(co) == LUA_YIELD ? lua_gettop(co) : lua_gettop(co) - 1);
- if (status != LUA_YIELD && status != LUA_OK) {
- LOG("ERROR", lua_tostring(co, -1));
- LOG("ERROR", "Error Lua Accept Method");
- }
- }
- }
- }
+static void TCP_IO_CB(CORE_P_ core_io *io, int revents) {
+ if (revents & EV_ERROR) {
+ LOG("ERROR", "Recevied a core_io object internal error from libev.");
+ return ;
+ }
+ lua_State *co = (lua_State *)core_get_watcher_userdata(io);
+ if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){
+ int status = CO_RESUME(co, NULL, 0);
+ if (status != LUA_YIELD && status != LUA_OK){
+ LOG("ERROR", lua_tostring(co, -1));
+ core_io_stop(CORE_LOOP_ io);
+ }
+ }
}
-int
-tcp_read(lua_State *L){
-
- errno = 0;
-
- int fd = lua_tointeger(L, 1);
- if (0 >= fd) return 0;
+static void IO_CONNECT(CORE_P_ core_io *io, int revents){
+ if (revents & EV_ERROR) {
+ LOG("ERROR", "Recevied a core_io object internal error from libev.");
+ return ;
+ }
+ if (revents & EV_WRITE){
+ lua_State *co = (lua_State *)core_get_watcher_userdata(io);
+ if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){
+ socklen_t len = sizeof(socklen_t); int err = 0; int connected = 0;
+ if(getsockopt(io->fd, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len) == 0 && err == 0) connected = 1;
+ lua_pushboolean(co, connected);
+ if (err) lua_pushstring(co, strerror(err));
+ int status = CO_RESUME(co, NULL, lua_gettop(co) - 1);
+ if (status != LUA_YIELD && status != LUA_OK){
+ LOG("ERROR", lua_tostring(co, -1));
+ core_io_stop(CORE_LOOP_ io);
+ }
+ }
+ }
+}
- int bytes = lua_tointeger(L, 2);
- if (0 >= bytes) return 0;
+/* 接受链接 */
+static void IO_ACCEPT(CORE_P_ core_io *io, int revents){
+ if (revents & EV_READ){
+ lua_State *co = (lua_State *) core_get_watcher_userdata(io);
+ int status = lua_status(co);
+ if (status != LUA_YIELD && status != LUA_OK) {
+ LOG("ERROR", "accept get a invalid lua vm.");
+ return;
+ }
+ for(;;) {
+ errno = 0;
+ struct sockaddr_in6 SA;
+ socklen_t slen = sizeof(SA);
+ memset(&SA, 0x0, slen);
+ int client = accept(io->fd, (struct sockaddr*)&SA, &slen);
+ if (0 >= client) {
+ if (errno != EWOULDBLOCK)
+ LOG("ERROR", strerror(errno));
+ return;
+ }
+ SETSOCKETOPT(client, None);
+ char buf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &SA.sin6_addr, buf, INET6_ADDRSTRLEN);
+ lua_pushinteger(co, client);
+ lua_pushlstring(co, buf, strlen(buf));
+ lua_pushinteger(co, SA.sin6_port);
+ status = CO_RESUME(co, NULL, status == LUA_YIELD ? lua_gettop(co) : lua_gettop(co) - 1);
+ if (status != LUA_YIELD && status != LUA_OK) {
+ LOG("ERROR", lua_tostring(co, -1));
+ LOG("ERROR", "Error Lua Accept Method");
+ core_io_stop(CORE_LOOP_ io);
+ }
+ }
+ }
+}
- do {
- char str[bytes];
- int rsize = read(fd, str, bytes);
+static void IO_ACCEPT_EX(CORE_P_ core_io *io, int revents) {
+ if (revents & EV_READ){
+ lua_State *co = (lua_State *) core_get_watcher_userdata(io);
+ int status = lua_status(co);
+ if (status != LUA_YIELD && status != LUA_OK) {
+ LOG("ERROR", "accept_ex get a invalid lua vm.");
+ return ;
+ }
+ for (;;) {
+ errno = 0;
+ struct sockaddr_un UN;
+ socklen_t slen = sizeof(UN);
+ memset(&UN, 0x0, slen);
+ int client = accept(io->fd, (struct sockaddr *)&UN, &slen);
+ if (0 >= client) {
+ if (errno != EWOULDBLOCK)
+ LOG("INFO", strerror(errno));
+ return ;
+ }
+ non_blocking(client);
+ lua_pushinteger(co, client);
+ // lua_pushlstring(co, UN.sun_path, strlen(UN.sun_path)); // unix domain path
+ status = CO_RESUME(co, NULL, status == LUA_YIELD ? lua_gettop(co) : lua_gettop(co) - 1);
+ if (status != LUA_YIELD && status != LUA_OK) {
+ LOG("ERROR", lua_tostring(co, -1));
+ LOG("ERROR", "Error Lua Accept Method");
+ core_io_stop(CORE_LOOP_ io);
+ }
+ }
+ }
+}
- if (rsize > 0) {
- lua_pushlstring(L, str, rsize);
- lua_pushinteger(L, rsize);
- return 2;
- }
- if (0 > rsize) {
- if (errno == EINTR) continue;
- }
- } while(0);
+struct io_sendfile {
+ int32_t fd;
+ off_t pos;
+ off_t offset;
+ lua_State *L;
+};
- return 0;
+static void
+IO_SENDFILE(CORE_P_ core_io *io, int revents){
+ if (revents & EV_WRITE){
+ errno = 0;
+ struct io_sendfile *sf = core_get_watcher_userdata(io);
+#if defined(__APPLE__) || defined(__FreeBSD__)
+ int tag = 0; off_t nBytes = 0;
+ for (;;) {
+ #if defined(__APPLE__)
+ tag = sendfile(sf->fd, io->fd, sf->pos, &nBytes, NULL, 0);
+ #else
+ tag = sendfile(sf->fd, io->fd, sf->pos, 0, NULL, &nBytes, SF_NODISKIO | SF_NOCACHE);
+ #endif
+ sf->pos += nBytes;
+ if (0 > tag) {
+ if (errno == EINTR) continue;
+ if (errno == EWOULDBLOCK) return;
+ lua_pushboolean(sf->L, 0);
+ break;
+ }
+ // 当nBytes与tag同时为0时说明发送成功, 其它情况下都当做发送失败.
+ if (0 == nBytes){ lua_pushboolean(sf->L, 1); break; }
+ }
+#elif defined(linux) || defined(__linux__)
+ #include
+ for (;;) {
+ int tag = sendfile(io->fd, sf->fd, NULL, sf->offset);
+ if (0 >= tag) {
+ if (!tag){ lua_pushboolean(sf->L, 1); break; }
+ if (errno == EINTR) continue;
+ if (errno == EWOULDBLOCK) return;
+ lua_pushboolean(sf->L, 0);
+ break;
+ }
+ }
+#else
+ char buf[sf->offset];
+ for(;;) {
+ int rBytes = pread(sf->fd, buf, sf->offset, sf->pos);
+ if (rBytes == 0) { lua_pushboolean(sf->L, 1); break; } // 所有数据写入发送完毕.
+ int wBytes = send(io->fd, buf, rBytes, MSG_DONTWAIT | MSG_NOSIGNAL);
+ if (wBytes <= 0) {
+ if (errno == EINTR) continue;
+ if (errno == EWOULDBLOCK) return;
+ lua_pushboolean(sf->L, 0);
+ break;
+ }
+ sf->pos += wBytes;
+ }
+#endif
+ core_set_watcher_userdata(io, NULL);
+ int status = CO_RESUME(sf->L, NULL, lua_status(sf->L) == LUA_YIELD ? lua_gettop(sf->L) : lua_gettop(sf->L) - 1);
+ if (status != LUA_YIELD && status != LUA_OK) {
+ LOG("ERROR", lua_tostring(sf->L, -1));
+ LOG("ERROR", "Error Lua SENDFILE Method");
+ }
+ close(sf->fd);
+ xfree(sf);
+ }
}
-int
-tcp_sslread(lua_State *L){
-
- errno = 0;
+static int tcp_sendfile(lua_State *L){
+ core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__");
+ int fd = open(luaL_checkstring(L, 3), O_RDONLY);
+ if (fd < 0)
+ return luaL_error(L, "[%s]: %s.", luaL_checkstring(L, 3), strerror(errno));
+
+ struct io_sendfile *sf = xmalloc(sizeof(struct io_sendfile));
+ sf->L = lua_tothread(L, 2);
+ sf->fd = fd;
+ sf->offset = luaL_checkinteger(L, 5);
+ sf->pos = 0;
+
+ core_set_watcher_userdata(io, sf);
+ core_io_init(io, IO_SENDFILE, luaL_checkinteger(L, 4), EV_WRITE);
+ core_io_start(CORE_LOOP_ io);
+ return 1;
+}
- SSL *ssl = lua_touserdata(L, 1);
- if (!ssl) return 0;
+static int tcp_peek(lua_State *L) {
+ errno = 0;
+
+ int fd = lua_tointeger(L, 1);
+ if (0 >= fd) return 0;
+
+ int bsize = lua_tointeger(L, 2);
+ char* buffer = lua_newuserdata(L, bsize);
+
+ if (0 == lua_toboolean(L, 3)) {
+ lua_pushinteger(L, read(fd, buffer, bsize));
+ return 1;
+ }
+
+ while (1){
+ int len = recv(fd, buffer, bsize, MSG_PEEK);
+ if (len <= 0){
+ if (errno == EINTR) continue;
+ if (errno == EWOULDBLOCK) {
+ lua_pushnil(L);
+ lua_pushinteger(L, 0);
+ break;
+ }
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ break;
+ }
+ lua_pushlstring(L, buffer, len);
+ lua_pushinteger(L, len);
+ break;
+ }
+ return 2;
+}
- int bytes = lua_tointeger(L, 2);
- if (0 >= bytes) return 0;
+static int tcp_sslpeek(lua_State *L) {
+ errno = 0;
+
+ SSL *ssl = lua_touserdata(L, 1);
+ if (!ssl) return 0;
+
+ int bsize = lua_tointeger(L, 2);
+ char* buffer = lua_newuserdata(L, bsize);
+
+ if (0 == lua_toboolean(L, 3)) {
+ lua_pushinteger(L, SSL_read(ssl, buffer, bsize));
+ return 1;
+ }
+
+ while (1){
+ int len = SSL_peek(ssl, buffer, bsize);
+ if (len <= 0){
+ if (errno == EINTR)
+ continue;
+ if (SSL_ERROR_WANT_READ == SSL_get_error(ssl, len)){
+ lua_pushnil(L);
+ lua_pushinteger(L, 0);
+ break;
+ }
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ break;
+ }
+ lua_pushlstring(L, buffer, len);
+ lua_pushinteger(L, len);
+ break;
+ }
+ return 2;
+}
- do {
- char str[bytes];
- int rsize = SSL_read(ssl, str, bytes);
- if (0 < rsize) {
- lua_pushlstring(L, str, rsize);
- lua_pushinteger(L, rsize);
- return 2;
- }
- if (0 > rsize){
- if (errno == EINTR) continue;
- if (SSL_ERROR_WANT_READ == SSL_get_error(ssl, rsize)){
- lua_pushnil(L);
- lua_pushinteger(L, 0);
- return 2;
- }
- }
- } while (0);
+static int tcp_read(lua_State *L){
+ int fd = lua_tointeger(L, 1);
+ if (0 >= fd)
+ return 0;
+
+ lua_Integer bytes = lua_tointeger(L, 2);
+ if (0 >= bytes)
+ return 0;
+
+ errno = 0;
+ char* str = NULL;
+ if (bytes <= MBSIZE)
+ str = alloca(bytes);
+ else
+ str = lua_newuserdata(L, bytes);
+
+ do {
+ int rsize = read(fd, str, bytes);
+ if (rsize > 0) {
+ lua_pushlstring(L, str, rsize);
+ lua_pushinteger(L, rsize);
+ return 2;
+ }
+ if (0 > rsize) {
+ if (errno == EINTR) continue;
+ if (errno == EWOULDBLOCK) {
+ lua_pushnil(L);
+ lua_pushinteger(L, 0);
+ return 2;
+ }
+ }
+ } while(0);
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ return 2;
+}
- return 0;
+static int tcp_sslread(lua_State *L){
+ SSL *ssl = lua_touserdata(L, 1);
+ if (!ssl)
+ return 0;
+
+ lua_Integer bytes = lua_tointeger(L, 2);
+ if (0 >= bytes)
+ return 0;
+
+ errno = 0;
+ char* str = NULL;
+ if (bytes <= MBSIZE)
+ str = alloca(bytes);
+ else
+ str = lua_newuserdata(L, bytes);
+
+ do {
+ int rsize = SSL_read(ssl, str, bytes);
+ if (0 < rsize) {
+ lua_pushlstring(L, str, rsize);
+ lua_pushinteger(L, rsize);
+ return 2;
+ }
+ if (0 > rsize){
+ if (errno == EINTR) continue;
+ if (SSL_ERROR_WANT_READ == SSL_get_error(ssl, rsize)){
+ lua_pushnil(L);
+ lua_pushinteger(L, 0);
+ return 2;
+ }
+ }
+ } while (0);
+ return 0;
}
-int
-tcp_write(lua_State *L){
+static int tcp_write(lua_State *L){
+ size_t resp_len = 0;
+ int fd = lua_tointeger(L, 1);
+ const char *response = luaL_checklstring(L, 2, &resp_len);
+ if (!response)
+ return luaL_error(L, "tcp_write ERROR: attempt to write an empty string.");
- errno = 0;
+ errno = 0;
+ int offset = lua_tointeger(L, 3);
+ do {
+ int wsize = send(fd, response + offset, resp_len - offset, MSG_DONTWAIT | MSG_NOSIGNAL);
+ if (wsize > 0) { lua_pushinteger(L, wsize); return 1; }
- int fd = lua_tointeger(L, 1);
- if (0 >= fd) return 0;
+ if (wsize < 0){
+ if (errno == EINTR) continue;
+ if (errno == EWOULDBLOCK){ lua_pushinteger(L, 0); return 1;}
+ }
- const char *response = lua_tostring(L, 2);
- if (!response) return 0;
+ } while (0);
- int resp_len = lua_tointeger(L, 3);
+ return 0;
+}
- do {
+static int tcp_sslwrite(lua_State *L){
+ SSL *ssl = lua_touserdata(L, 1);
+ if (!ssl)
+ return 0;
+
+ const char *response = lua_tostring(L, 2);
+ if (!response)
+ return luaL_error(L, "tcp_sslwrite ERROR: attempt to write an empty string.");
+
+ errno = 0;
+ int resp_len = lua_tointeger(L, 3);
+ do {
+ int wsize = SSL_write(ssl, response, resp_len);
+ if (wsize > 0) { lua_pushinteger(L, wsize); return 1; }
+ if (wsize < 0){
+ if (errno == EINTR) continue;
+ if (SSL_ERROR_WANT_WRITE == SSL_get_error(ssl, wsize)){ lua_pushinteger(L, 0); return 1; }
+ }
+ } while (0);
+
+ return 0;
+}
- int wsize = write(fd, response, resp_len);
+static int new_server_fd(lua_State *L){
+ const char *ip = lua_tostring(L, 1);
+ if (!ip)
+ return 0;
- if (wsize > 0) { lua_pushinteger(L, wsize); return 1; }
+ int port = lua_tointeger(L, 2);
+ if (!port)
+ return 0;
- if (wsize < 0){
- if (errno == EINTR) continue;
- if (errno == EAGAIN){ lua_pushinteger(L, 0); return 1;}
- }
+ int backlog = lua_tointeger(L, 3);
- } while (0);
+ int fd = create_server_fd(ip, port, 0 >= backlog ? 128 : backlog);
+ if (0 >= fd)
+ return 0;
- return 0;
+ lua_pushinteger(L, fd);
+ return 1;
}
-int
-tcp_sslwrite(lua_State *L){
-
- SSL *ssl = lua_touserdata(L, 1);
- if (!ssl) return 0;
-
- const char *response = lua_tostring(L, 2);
- if (!response) return 0;
-
- int resp_len = lua_tointeger(L, 3);
+static int new_client_fd(lua_State *L){
+ const char *ip = lua_tostring(L, 1);
+ if (!ip)
+ return 0;
- errno = 0;
+ int port = lua_tointeger(L, 2);
+ if (!port)
+ return 0;
- do {
- int wsize = SSL_write(ssl, response, resp_len);
- if (wsize > 0) { lua_pushinteger(L, wsize); return 1; }
- if (wsize < 0){
- if (errno == EINTR) continue;
- if (SSL_ERROR_WANT_WRITE == SSL_get_error(ssl, wsize)){ lua_pushinteger(L, 0); return 1; }
- }
- } while (0);
+ int fd = create_client_fd(ip, port);
+ if (0 >= fd)
+ return 0;
- return 0;
+ lua_pushinteger(L, fd);
+ return 1;
}
-int
-new_server_fd(lua_State *L){
- const char *ip = lua_tostring(L, 1);
- if(!ip) return 0;
+static int new_server_unixsock_fd(lua_State *L) {
+ size_t size = 0;
+ const char* path = luaL_checklstring(L, 1, &size);
+ if (!path || size < 2)
+ return 0;
- int port = lua_tointeger(L, 2);
- if(!port) return 0;
+ /* 传递rm为非nil与false值, 删除已经存在的文件 */
+ int rm = lua_toboolean(L, 2);
- int backlog = lua_tointeger(L, 3);
+ /* 文件存在或无法删除的情况下都将创建unixsock失败 */
+ if (!access(path, F_OK)) {
+ if (!rm || unlink(path))
+ return 0;
+ }
- int fd = create_server_fd(port, 0 >= backlog ? 128 : backlog);
- if (0 >= fd) return 0;
+ int backlog = luaL_checkinteger(L, 3);
- lua_pushinteger(L, fd);
+ int fd = create_server_unixsock(path, size, 0 >= backlog ? 128 : backlog);
+ if (fd <= 0)
+ return 0;
- return 1;
+ lua_pushinteger(L, fd);
+ return 1;
}
-int
-new_client_fd(lua_State *L){
- const char *ip = lua_tostring(L, 1);
- if(!ip) return 0;
+static int new_client_unixsock_fd(lua_State *L){
+ size_t size = 0;
+ const char* path = luaL_checklstring(L, 1, &size);
+ if (!path || size < 2)
+ return 0;
- int port = lua_tointeger(L, 2);
- if(!port) return 0;
+ // 如果文件不存在返回失败
+ if (access(path, F_OK))
+ return 0;
- int fd = create_client_fd(ip, port);
- if (0 >= fd) return 0;
+ int fd = create_client_unixsock(path, size);
+ if (fd <= 0)
+ return 0;
- lua_pushinteger(L, fd);
-
- return 1;
+ lua_pushinteger(L, fd);
+ return 1;
}
-int
-tcp_listen(lua_State *L){
- core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__");
- if(!io) return 0;
-
- /* socket文件描述符 */
- int fd = lua_tointeger(L, 2);
- if (0 >= fd) return 0;
+static int tcp_listen(lua_State *L){
+ core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__");
+ if (!io)
+ return 0;
+
+ /* socket文件描述符 */
+ int fd = lua_tointeger(L, 2);
+ if (0 >= fd)
+ return 0;
+
+ /* 回调协程 */
+ lua_State *co = lua_tothread(L, 3);
+ if (!co)
+ return 0;
+
+ core_set_watcher_userdata(io, co);
+ core_io_init(io, IO_ACCEPT, fd, EV_READ);
+ core_io_start(CORE_LOOP_ io);
+ return 1;
+}
- /* 回调协程 */
- lua_State *co = lua_tothread(L, 3);
- if (!co) return 0;
+static int tcp_listen_ex(lua_State *L) {
+ core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__");
+ if (!io)
+ return 0;
+
+ /* socket文件描述符 */
+ int fd = lua_tointeger(L, 2);
+ if (0 >= fd)
+ return 0;
+ /* 回调协程 */
+ lua_State *co = lua_tothread(L, 3);
+ if (!co)
+ return 0;
+
+ core_set_watcher_userdata(io, co);
+ core_io_init(io, IO_ACCEPT_EX, fd, EV_READ);
+ core_io_start(CORE_LOOP_ io);
+ return 1;
+}
- core_set_watcher_userdata(io, co);
+static int tcp_connect(lua_State *L){
+ core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__");
+ if(!io) return 0;
- core_io_init(io, IO_ACCEPT, fd, EV_READ);
+ /* socket文件描述符 */
+ int fd = lua_tointeger(L, 2);
+ if (0 >= fd)
+ return 0;
- core_io_start(CORE_LOOP_ io);
+ /* 回调协程 */
+ lua_State *co = lua_tothread(L, 3);
+ if (!co)
+ return 0;
- return 0;
+ core_set_watcher_userdata(io, co);
+ core_io_init(io, IO_CONNECT, fd, EV_READ | EV_WRITE);
+ core_io_start(CORE_LOOP_ io);
+ return 0;
}
-int
-tcp_connect(lua_State *L){
-
- core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__");
- if(!io) return 0;
-
- /* socket文件描述符 */
- int fd = lua_tointeger(L, 2);
- if (0 >= fd) return 0;
-
- /* 回调协程 */
- lua_State *co = lua_tothread(L, 3);
- if (!co) return 0;
+static int tcp_sslconnect(lua_State *L){
+
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (!ssl)
+ return 0;
+
+ int reason = SSL_get_error(ssl, SSL_do_handshake(ssl));
+ /* 握手结束 -> 握手成功 */
+ if (reason == SSL_ERROR_NONE){
+ lua_pushboolean(L, 1);
+ return 1;
+ }
+ /* 需要再进一步交互 */
+ if (SSL_ERROR_WANT_READ == reason || SSL_ERROR_WANT_WRITE == reason){
+ lua_pushnil(L);
+ lua_pushinteger(L, reason - 1);
+ return 2;
+ }
+ /* 握手失败 */
+ return 0;
+}
- core_set_watcher_userdata(io, co);
+static int tcp_start(lua_State *L){
+ core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__");
+ if(!io)
+ return 0;
+
+ /* socket文件描述符 */
+ int fd = lua_tointeger(L, 2);
+ if (0 >= fd)
+ return 0;
+
+ /* 监听事件 */
+ int events = lua_tointeger(L, 3);
+ if (0 >= events || events > 3)
+ return 0;
+
+ /* 回调协程 */
+ lua_State *co = lua_tothread(L, 4);
+ if (!co)
+ return 0;
+
+ core_set_watcher_userdata(io, co);
+ core_io_init(io, TCP_IO_CB, fd, events);
+ core_io_start(CORE_LOOP_ io);
+ return 0;
+}
- core_io_init(io, IO_CONNECT, fd, EV_READ | EV_WRITE);
+// 设置SSL客户端的SNI特性
+static int ssl_set_connect_server(lua_State *L) {
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (!ssl)
+ return luaL_error(L, "Invalid SSL ssl.");
- core_io_start(CORE_LOOP_ io);
+ size_t size = 0;
+ const char *hostname = (const char *)luaL_checklstring(L, 2, &size);
+ if (!hostname || size < 1)
+ return luaL_error(L, "Invalid host name.");
- return 0;
+#if defined(SSL_set_tlsext_host_name)
+ SSL_set_tlsext_host_name(ssl, hostname);
+#endif
+ return 1;
}
-int
-tcp_sslconnect(lua_State *L){
+static int ssl_set_connect_mode(lua_State *L) {
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (!ssl)
+ return luaL_error(L, "Invalid SSL ssl.");
- SSL *ssl = (SSL*) lua_touserdata(L, 1);
- if (!ssl) return 0;
+ SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2);
+ if (!ctx)
+ return luaL_error(L, "Invalid SSL_CTX ctx.");
- int status = SSL_do_handshake(ssl);
- if (1 == status) {
- lua_pushboolean(L, 1);
- return 1;
- }
- if (SSL_ERROR_WANT_READ == SSL_get_error(ssl, status)) {
- lua_pushnil(L);
- lua_pushinteger(L, EV_READ);
- return 2;
- }
- if (SSL_ERROR_WANT_WRITE == SSL_get_error(ssl, status)){
- lua_pushnil(L);
- lua_pushinteger(L, EV_WRITE);
- return 2;
- }
- return 0;
+ SSL_set_connect_state(ssl);
+ return 1;
}
-int
-tcp_start(lua_State *L){
+static int ssl_set_accept_mode(lua_State *L) {
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (!ssl)
+ return luaL_error(L, "Invalid SSL ssl.");
- core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__");
- if(!io) return 0;
+ SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2);
+ if (!ctx)
+ return luaL_error(L, "Invalid SSL_CTX ctx.");
- /* socket文件描述符 */
- int fd = lua_tointeger(L, 2);
- if (0 >= fd) return 0;
+ SSL_set_accept_state(ssl);
- /* 监听事件 */
- int events = lua_tointeger(L, 3);
- if (0 >= events || events > 3) return 0;
+ return 1;
+}
- /* 回调协程 */
- lua_State *co = lua_tothread(L, 4);
- if (!co) return 0;
+// 加载证书
+static int ssl_set_certificate(lua_State *L) {
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (!ssl)
+ return luaL_error(L, "Invalid SSL ssl.");
+
+ SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2);
+ if (!ctx)
+ return luaL_error(L, "Invalid SSL_CTX ctx.");
+
+ size_t size = 0;
+ const char* path = luaL_checklstring(L, 3, &size);
+ if (!path || size < 1)
+ return luaL_error(L, "Invalid cert path");
+
+ X509 *cert;
+ /* 为`SSL`设置证书, 所有加载策略都失败就返回失败. */
+ BIO* IO = BIO_new(BIO_s_mem()); BIO_write(IO, path, size);
+ cert = PEM_read_bio_X509(IO, NULL, NULL, NULL);
+ BIO_free(IO);
+
+ /* 内存证书 */
+ if (cert && 1 == SSL_use_certificate(ssl, cert))
+ return 0;
+
+ FILE* fp = fopen(path, "rb");
+ cert = PEM_read_X509(fp, NULL, NULL, NULL);
+ if (fp)
+ fclose(fp);
+
+ /* 文件证书 */
+ if (cert && 1 == SSL_use_certificate(ssl, cert))
+ return 0;
+
+ return luaL_error(L, "[ssl error]: read cert failed.");
+}
- core_set_watcher_userdata(io, co);
+// 加载私钥
+static int ssl_set_privatekey(lua_State *L) {
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (!ssl)
+ return luaL_error(L, "Invalid SSL ssl.");
+
+ SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2);
+ if (!ctx)
+ return luaL_error(L, "Invalid SSL_CTX ctx.");
+
+ size_t size = 0;
+ const char* path = luaL_checklstring(L, 3, &size);
+ if (!path || size < 1)
+ return luaL_error(L, "Invalid cert path");
+
+ EVP_PKEY *key;
+ /* 为`SSL`设置私钥, 所有加载策略都失败就返回失败. */
+ BIO* IO = BIO_new(BIO_s_mem()); BIO_write(IO, path, size);
+ key = PEM_read_bio_PrivateKey(IO, NULL, NULL, NULL);
+ BIO_free(IO);
+
+ /* 内存私钥 */
+ if (key && 1 == SSL_use_PrivateKey(ssl, key))
+ return 0;
+
+ FILE* fp = fopen(path, "rb");
+ key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
+ if (fp)
+ fclose(fp);
+
+ /* 文件私钥 */
+ if (key && 1 == SSL_use_PrivateKey(ssl, key))
+ return 0;
+
+ return luaL_error(L, "[ssl error]: read private key failed.");
+}
- core_io_init(io, TCP_IO_CB, fd, events);
+// 如果私钥有安装密钥, 则再这里设置
+static int ssl_set_userdata_key(lua_State *L) {
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (!ssl)
+ return luaL_error(L, "Invalid SSL ssl.");
- core_io_start(CORE_LOOP_ io);
+ SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2);
+ if (!ctx)
+ return luaL_error(L, "Invalid SSL_CTX ctx.");
- return 0;
+ size_t size = 0;
+ const char* password = luaL_checklstring(L, 3, &size);
+ if (!password || size < 1)
+ return 1;
+ SSL_CTX_set_default_passwd_cb_userdata(ctx, (void*)password);
+ return 1;
}
-int
-ssl_new(lua_State *L){
+// 验证证书与私钥是否有效
+static int ssl_verify(lua_State *L) {
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (!ssl)
+ return luaL_error(L, "Invalid SSL ssl.");
- int fd = lua_tointeger(L, 1);
+ SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2);
+ if (!ctx)
+ return luaL_error(L, "Invalid SSL_CTX ctx.");
- SSL_CTX *ssl_ctx = SSL_CTX_new(SSLv23_method());
- if (!ssl_ctx) return 0;
+ // 检查证书与私钥是否一致.
+ if (1 != SSL_check_private_key(ssl) || 1 != SSL_CTX_check_private_key(ctx))
+ return 0;
- SSL *ssl = SSL_new(ssl_ctx);
- if (!ssl) return 0;
+ lua_pushboolean(L, 1);
+ return 1;
+}
- SSL_set_fd(ssl, fd);
+static int ssl_set_alpn(lua_State *L) {
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (!ssl)
+ return luaL_error(L, "Invalid SSL ssl.");
- SSL_set_connect_state(ssl);
+ SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2);
+ if (!ctx)
+ return luaL_error(L, "Invalid SSL_CTX ctx.");
- lua_pushlightuserdata(L, (void*) ssl_ctx);
+ size_t lsize = 0;
+ const char *str = luaL_checklstring(L, 3, &lsize);
+ if (!str || lsize < 1)
+ return 0;
- lua_pushlightuserdata(L, (void*) ssl);
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ SSL_set_alpn_protos(ssl, (const unsigned char *)str, lsize);
+ SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)str, lsize);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
- return 2;
+ return 1;
}
-int
-ssl_free(lua_State *L){
+static int ssl_get_alpn(lua_State *L) {
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (!ssl)
+ return luaL_error(L, "Invalid SSL ssl.");
+
+ SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2);
+ if (!ctx)
+ return luaL_error(L, "Invalid SSL_CTX ctx.");
+
+ uint32_t len = 0;
+ const uint8_t *data = NULL;
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ SSL_get0_alpn_selected(ssl, (const uint8_t **)&data, (unsigned int *)&len);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+ if (!data || len < 1)
+ return 0;
+ lua_pushlstring(L, (const char *)data, len);
+ return 1;
+}
- SSL_CTX *ssl_ctx = (SSL_CTX*) lua_touserdata(L, 1);
- if (ssl_ctx) SSL_CTX_free(ssl_ctx); // 销毁ctx上下文;
+static int ssl_new(lua_State *L){
+ SSL_CTX *ssl_ctx = SSL_CTX_new(SSLv23_method());
+ if (!ssl_ctx)
+ return 0;
- SSL *ssl = (SSL*) lua_touserdata(L, 2);
- if (ssl) SSL_free(ssl); // 销毁基于ctx的ssl对象;
+ SSL *ssl = SSL_new(ssl_ctx);
+ if (!ssl)
+ return 0;
- return 0;
+ lua_pushlightuserdata(L, (void*) ssl);
+ lua_pushlightuserdata(L, (void*) ssl_ctx);
+ return 2;
}
-int
-tcp_new(lua_State *L){
-
- core_io *io = (core_io *) lua_newuserdata(L, sizeof(core_io));
+static int ssl_set_fd(lua_State *L) {
- if(!io) return 0;
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (!ssl)
+ return luaL_error(L, "Invalid SSL ssl.");
- luaL_setmetatable(L, "__TCP__");
-
- return 1;
+ int fd = lua_tointeger(L, 2);
+ SSL_set_fd(ssl, fd);
+ SSL_set_connect_state(ssl);
+ return 1;
}
+static int ssl_new_fd(lua_State *L){
-int
-tcp_stop(lua_State *L){
+ int fd = lua_tointeger(L, 1);
- core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__");
- if(!io) return 0;
+ SSL_CTX *ssl_ctx = SSL_CTX_new(SSLv23_method());
+ if (!ssl_ctx)
+ return 0;
- core_io_stop(CORE_LOOP_ io);
+ SSL *ssl = SSL_new(ssl_ctx);
+ if (!ssl)
+ return 0;
- return 0;
+ SSL_set_fd(ssl, fd);
+ SSL_set_connect_state(ssl);
+ lua_pushlightuserdata(L, (void*) ssl);
+ lua_pushlightuserdata(L, (void*) ssl_ctx);
+ return 2;
+}
+static int ssl_free(lua_State *L){
+ SSL *ssl = (SSL*) lua_touserdata(L, 1);
+ if (ssl)
+ SSL_free(ssl); // 销毁基于ctx的ssl对象;
+ SSL_CTX *ssl_ctx = (SSL_CTX*) lua_touserdata(L, 2);
+ if (ssl_ctx)
+ SSL_CTX_free(ssl_ctx); // 销毁ctx上下文;
+ return 0;
}
-int
-tcp_close(lua_State *L){
+static int tcp_new(lua_State *L){
+ core_io *io = (core_io *) lua_newuserdata(L, sizeof(core_io));
+ if(!io)
+ return 0;
+ luaL_setmetatable(L, "__TCP__");
+ return 1;
+}
- int fd = lua_tointeger(L, 1);
- if (fd && fd > 0) close(fd);
+static int tcp_stop(lua_State *L){
+ core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__");
+ if(!io)
+ return 0;
+ core_io_stop(CORE_LOOP_ io);
+ return 0;
+}
- return 0;
+static int tcp_close(lua_State *L){
+ int fd = lua_tointeger(L, 1);
+ if (fd && fd > 0)
+ close(fd);
+ return 0;
+}
+/* 修改写缓冲区大小 */
+static int tcp_set_write_buf(lua_State *L) {
+ int fd = lua_tointeger(L, 1);
+ if (fd < 0)
+ return 0;
+ int bsize = lua_tointeger(L, 2);
+ if (bsize <= 65535)
+ return 0;
+
+#if defined(SO_SNDBUF)
+ if (-1 == setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &bsize, sizeof(socklen_t)))
+ return 0;
+#endif
+
+ return 1;
}
-LUAMOD_API int
-luaopen_tcp(lua_State *L){
- luaL_checkversion(L);
- /* 添加SSL支持 */
- SSL_library_init();
- SSL_load_error_strings();
- // CRYPTO_set_mem_functions(xmalloc, xrealloc, xfree);
- // OpenSSL_add_ssl_algorithms();
- /* 添加SSL支持 */
- luaL_newmetatable(L, "__TCP__");
- lua_pushstring (L, "__index");
- lua_pushvalue(L, -2);
- lua_rawset(L, -3);
- lua_pushliteral(L, "__mode");
- lua_pushliteral(L, "kv");
- lua_rawset(L, -3);
+/* 修改读缓冲区大小 */
+static int tcp_set_read_buf(lua_State *L) {
+ int fd = lua_tointeger(L, 1);
+ if (fd < 0)
+ return 0;
+ int bsize = lua_tointeger(L, 2);
+ if (bsize <= 65535)
+ return 0;
+
+#if defined(SO_RCVBUF)
+ if (-1 == setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bsize, sizeof(socklen_t)))
+ return 0;
+#endif
+
+ return 1;
+}
- luaL_Reg tcp_libs[] = {
- {"read", tcp_read},
- {"write", tcp_write},
- {"ssl_read", tcp_sslread},
- {"ssl_write", tcp_sslwrite},
- {"stop", tcp_stop},
- {"start", tcp_start},
- {"close", tcp_close},
- {"listen", tcp_listen},
- {"connect", tcp_connect},
- {"ssl_connect", tcp_sslconnect},
- {"new", tcp_new},
- {"new_ssl", ssl_new},
- {"free_ssl", ssl_free},
- {"new_server_fd", new_server_fd},
- {"new_client_fd", new_client_fd},
- {NULL, NULL}
- };
- luaL_setfuncs(L, tcp_libs, 0);
- luaL_newlib(L, tcp_libs);
- return 1;
+LUAMOD_API int luaopen_tcp(lua_State *L){
+ luaL_checkversion(L);
+ /* 添加SSL支持 */
+ SSL_library_init();
+ // SSL_load_error_strings();
+ // ERR_load_crypto_strings();
+ // OpenSSL_add_ssl_algorithms();
+ // CRYPTO_set_mem_functions(xmalloc, xrealloc, xfree);
+ /* 添加SSL支持 */
+ luaL_newmetatable(L, "__TCP__");
+ lua_pushstring (L, "__index");
+ lua_pushvalue(L, -2);
+ lua_rawset(L, -3);
+ lua_pushliteral(L, "__mode");
+ lua_pushliteral(L, "kv");
+ lua_rawset(L, -3);
+ luaL_Reg tcp_libs[] = {
+ {"read", tcp_read},
+ {"write", tcp_write},
+ {"peek", tcp_peek},
+ {"sslpeek", tcp_sslpeek},
+ {"ssl_read", tcp_sslread},
+ {"ssl_write", tcp_sslwrite},
+ {"stop", tcp_stop},
+ {"start", tcp_start},
+ {"close", tcp_close},
+ {"listen", tcp_listen},
+ {"listen_ex", tcp_listen_ex},
+ {"connect", tcp_connect},
+ {"ssl_connect", tcp_sslconnect},
+ {"new", tcp_new},
+ {"new_ssl", ssl_new},
+ {"ssl_set_fd", ssl_set_fd},
+ {"new_ssl_fd", ssl_new_fd},
+ {"free_ssl", ssl_free},
+ {"new_server_fd", new_server_fd},
+ {"new_client_fd", new_client_fd},
+ {"new_server_unixsock_fd", new_server_unixsock_fd},
+ {"new_client_unixsock_fd", new_client_unixsock_fd},
+ {"sendfile", tcp_sendfile},
+ {"ssl_verify", ssl_verify},
+ {"ssl_set_alpn", ssl_set_alpn},
+ {"ssl_get_alpn", ssl_get_alpn},
+ {"tcp_set_read_buf", tcp_set_read_buf},
+ {"tcp_set_write_buf", tcp_set_write_buf},
+ {"ssl_set_accept_mode", ssl_set_accept_mode},
+ {"ssl_set_connect_mode", ssl_set_connect_mode},
+ {"ssl_set_privatekey", ssl_set_privatekey},
+ {"ssl_set_certificate", ssl_set_certificate},
+ {"ssl_set_userdata_key", ssl_set_userdata_key},
+ {"ssl_set_connect_server", ssl_set_connect_server},
+ {NULL, NULL}
+ };
+ luaL_setfuncs(L, tcp_libs, 0);
+ luaL_newlib(L, tcp_libs);
+ return 1;
}
diff --git a/luaclib/src/ltimer.c b/luaclib/src/ltimer.c
index 71c0c3d8..83bd2eb6 100644
--- a/luaclib/src/ltimer.c
+++ b/luaclib/src/ltimer.c
@@ -1,92 +1,67 @@
#define LUA_LIB
-#include "../../src/core.h"
+#include
/* === 定时器 === */
-static void
-TIMEOUT_CB(CORE_P_ core_timer *timer, int revents){
-
- if (revents & EV_TIMER){
-
- lua_State *co = (lua_State *) core_get_watcher_userdata(timer);
-
- int status = lua_resume(co, NULL, lua_gettop(co) > 0 ? lua_gettop(co) - 1 : 0);
-
- if (status != LUA_OK && status != LUA_YIELD){
-
- LOG( "ERROR", lua_tostring(co, -1));
-
+static void TIMEOUT_CB(CORE_P_ core_timer *timer, int revents){
+ if (revents & EV_TIMER){
+ lua_State *co = (lua_State *) core_get_watcher_userdata(timer);
+ int status = CO_RESUME(co, NULL, lua_gettop(co) > 0 ? lua_gettop(co) - 1 : 0);
+ if (status != LUA_OK && status != LUA_YIELD){
+ LOG( "ERROR", lua_tostring(co, -1));
core_timer_stop(CORE_LOOP_ timer);
-
- }
- }
+ }
+ }
}
-int
-timer_stop(lua_State *L){
-
+static int timer_stop(lua_State *L){
core_timer *timer = (core_timer *) luaL_testudata(L, 1, "__TIMER__");
- if(!timer) return 0;
-
+ if(!timer)
+ return 0;
core_timer_stop(CORE_LOOP_ timer);
-
return 0;
}
-int
-timer_start(lua_State *L){
-
+static int timer_start(lua_State *L){
core_timer *timer = (core_timer *) luaL_testudata(L, 1, "__TIMER__");
- if(!timer) return 0;
-
+ if(!timer)
+ return 0;
lua_Number timeout = luaL_checknumber(L, 2);
- if (timeout <= 0 ) return 0;
-
+ if (timeout <= 0 )
+ return 0;
lua_State *co = lua_tothread(L, 3);
- if(!co) return 0;
-
+ if(!co)
+ return 0;
core_set_watcher_userdata(timer, (void*)co);
-
core_timer_start(CORE_LOOP, timer, timeout);
-
return 0;
-
}
-int
-timer_new(lua_State *L){
-
+static int timer_new(lua_State *L){
core_timer *timer = (core_timer *) lua_newuserdata(L, sizeof(core_timer));
- if(!timer) return 0;
-
+ if(!timer)
+ return 0;
core_timer_init(timer, TIMEOUT_CB);
-
luaL_setmetatable(L, "__TIMER__");
-
return 1;
-
}
-LUAMOD_API int
-luaopen_timer(lua_State *L){
-
+LUAMOD_API int luaopen_timer(lua_State *L){
luaL_checkversion(L);
-
- luaL_newmetatable(L, "__TIMER__");
- lua_pushstring (L, "__index");
- lua_pushvalue(L, -2);
- lua_rawset(L, -3);
- lua_pushliteral(L, "__mode");
- lua_pushliteral(L, "kv");
- lua_rawset(L, -3);
-
+ luaL_newmetatable(L, "__TIMER__");
+ lua_pushstring (L, "__index");
+ lua_pushvalue(L, -2);
+ lua_rawset(L, -3);
+ lua_pushliteral(L, "__mode");
+ lua_pushliteral(L, "kv");
+ lua_rawset(L, -3);
luaL_Reg timer_libs[] = {
{"new", timer_new},
{"stop", timer_stop},
{"start", timer_start},
{NULL, NULL},
};
- luaL_setfuncs(L, timer_libs, 0);
+ luaL_setfuncs(L, timer_libs, 0);
luaL_newlib(L, timer_libs);
- return 1;
+ return 1;
}
diff --git a/luaclib/src/ludp.c b/luaclib/src/ludp.c
index 14be3707..7a69247f 100644
--- a/luaclib/src/ludp.c
+++ b/luaclib/src/ludp.c
@@ -1,46 +1,88 @@
#define LUA_LIB
-#include "../../src/core.h"
+#include
-int
-udp_socket_new(const char *ipaddr, int port){
+#ifndef alloca
+ #define alloca __alloca
+#endif
- errno = 0;
- /* 建立socket*/
- int sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
- if (0 >= sockfd) return -1;
+#define MBSIZE (262144)
+static inline void SETSOCKETOPT(int sockfd) {
+ int Enable = 1;
+ int ret = 0;
/* 设置非阻塞 */
non_blocking(sockfd);
- int ENABLE = 1;
- /* 端口重用 */
- setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &ENABLE, sizeof(ENABLE));
+/* 地址重用 */
+#ifdef SO_REUSEADDR
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &Enable, sizeof(Enable));
+ if (ret < 0) {
+ LOG("ERROR", "Setting SO_REUSEADDR failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+#endif
+
+/* 端口重用 */
+#ifdef SO_REUSEPORT
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &Enable, sizeof(Enable));
+ if (ret < 0) {
+ LOG("ERROR", "Setting SO_REUSEPORT failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+#endif
+
+/* 开启IPV6与ipv4双栈 */
+#ifdef IPV6_V6ONLY
+ int No = 0;
+ ret = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&No, sizeof(No));
+ if (ret < 0){
+ LOG("ERROR", "Setting IPV6_V6ONLY failed.");
+ LOG("ERROR", strerror(errno));
+ return core_exit();
+ }
+#endif
+
+}
+static int udp_socket_new(const char *ipaddr, int port){
+ errno = 0;
+ /* 建立 UDP Socket */
+ int sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (0 >= sockfd)
+ return -1;
+ SETSOCKETOPT(sockfd);
struct sockaddr_in6 SA;
+ memset(&SA, 0x0, sizeof(SA));
SA.sin6_family = AF_INET6;
SA.sin6_port = htons(port);
- inet_pton(AF_INET6, ipaddr, &SA.sin6_addr);
-
- connect(sockfd, (struct sockaddr*)&SA, sizeof(SA));
-
+ int error = inet_pton(AF_INET6, ipaddr, &SA.sin6_addr);
+ if (1 != error) {
+ LOG("ERROR", strerror(errno));
+ close(sockfd);
+ return -1;
+ }
+ int ret = connect(sockfd, (struct sockaddr*)&SA, sizeof(SA));
+ if (ret == -1) {
+ LOG("ERROR", strerror(errno));
+ close(sockfd);
+ return -1;
+ }
return sockfd;
}
-static void
-UDP_IO_CB(CORE_P_ ev_io *io, int revents){
-
+static void UDP_IO_CB(CORE_P_ core_io *io, int revents){
int status = 0;
-
if (revents & EV_ERROR) {
- LOG("ERROR", "Recevied a ev_io object internal error from libev.");
+ LOG("ERROR", "Recevied a core_io object internal error from libev.");
return ;
}
-
if (revents & EV_READ){
lua_State *co = (lua_State *)core_get_watcher_userdata(io);
if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){
- status = lua_resume(co, NULL, lua_gettop(co) > 0 ? lua_gettop(co) - 1 : 0);
+ status = CO_RESUME(co, NULL, lua_gettop(co) > 0 ? lua_gettop(co) - 1 : 0);
if (status != LUA_YIELD && status != LUA_OK){
LOG("ERROR", lua_tostring(co, -1));
}
@@ -48,147 +90,106 @@ UDP_IO_CB(CORE_P_ ev_io *io, int revents){
}
}
-int
-udp_send(lua_State *L){
-
+static int udp_send(lua_State *L){
int fd = lua_tointeger(L, 1);
- if (fd < 0) return 0;
-
+ if (fd < 0)
+ return 0;
const char* data = lua_tostring(L, 2);
- if (!data) return 0;
-
+ if (!data)
+ return 0;
size_t len = lua_tointeger(L, 3);
-
int wsize = write(fd, data, len);
-
lua_pushinteger(L, wsize);
-
return 1;
-
}
-int
-udp_recv(lua_State *L){
-
+static int udp_recv(lua_State *L){
int fd = lua_tointeger(L, 1);
- if (fd < 0) return 0;
-
- char str[4096] = {0};
-
- int rsize = read(fd, str, 4096);
-
- if (rsize < 0) return 0;
-
+ if (fd < 0)
+ return 0;
+ int bsize = MBSIZE;
+ char* str = alloca(MBSIZE);
+ int rsize = read(fd, str, bsize);
+ if (rsize < 0)
+ return 0;
lua_pushlstring(L, str, rsize);
-
lua_pushinteger(L, rsize);
-
return 2;
-
}
-int
-udp_connect(lua_State *L){
-
+static int udp_connect(lua_State *L){
const char *ip = lua_tostring(L, 1);
- if(!ip) {lua_settop(L, 0); return 0;}
-
+ if(!ip)
+ return 0;
int port = lua_tointeger(L, 2);
- if(!port) {lua_settop(L, 0); return 0;}
-
+ if(!port)
+ return 0;
int fd = udp_socket_new(ip, port);
-
+ if (0 >= fd)
+ return 0;
lua_pushinteger(L, fd > 0 ? fd : -1);
-
return 1;
-
}
-int
-udp_start(lua_State *L){
-
- ev_io *io = (ev_io *) luaL_testudata(L, 1, "__UDP__");
+static int udp_start(lua_State *L){
+ core_io *io = (core_io *) luaL_testudata(L, 1, "__UDP__");
if(!io) return 0;
-
int fd = lua_tointeger(L, 2);
- if (fd < 0) return 0;
-
+ if (fd < 0)
+ return 0;
/* 回调协程 */
lua_State *co = lua_tothread(L, 3);
- if (!co) return 0;
-
+ if (!co)
+ return 0;
core_set_watcher_userdata(io, co);
-
core_io_init (io, UDP_IO_CB, fd, EV_READ);
-
core_io_start (CORE_LOOP_ io);
-
return 0;
-
}
-int
-udp_stop(lua_State *L){
-
- ev_io *io = (ev_io *) luaL_testudata(L, 1, "__UDP__");
- if(!io) return 0;
-
+static int udp_stop(lua_State *L){
+ core_io *io = (core_io *) luaL_testudata(L, 1, "__UDP__");
+ if(!io)
+ return 0;
core_io_stop(CORE_LOOP_ io);
-
return 0;
-
}
-int
-udp_close(lua_State *L){
-
+static int udp_close(lua_State *L){
int fd = lua_tointeger(L, 1);
-
- if (fd && fd > 0) close(fd);
-
+ if (fd && fd > 0)
+ close(fd);
return 0;
-
}
-
-
-int
-udp_new(lua_State *L){
-
- ev_io *io = (ev_io *) lua_newuserdata(L, sizeof(ev_io));
-
- if(!io) return 0;
-
+static int udp_new(lua_State *L){
+ core_io *io = (core_io *) lua_newuserdata(L, sizeof(core_io));
+ if(!io)
+ return 0;
luaL_setmetatable(L, "__UDP__");
-
return 1;
-
}
-LUAMOD_API int
-luaopen_udp(lua_State *L){
-
+LUAMOD_API int luaopen_udp(lua_State *L){
luaL_checkversion(L);
-
- luaL_newmetatable(L, "__UDP__");
- lua_pushstring (L, "__index");
- lua_pushvalue(L, -2);
- lua_rawset(L, -3);
- lua_pushliteral(L, "__mode");
- lua_pushliteral(L, "kv");
- lua_rawset(L, -3);
-
+ luaL_newmetatable(L, "__UDP__");
+ lua_pushstring (L, "__index");
+ lua_pushvalue(L, -2);
+ lua_rawset(L, -3);
+ lua_pushliteral(L, "__mode");
+ lua_pushliteral(L, "kv");
+ lua_rawset(L, -3);
luaL_Reg udp_libs[] = {
{"new", udp_new},
{"close", udp_close},
- {"start", udp_start},
- {"stop", udp_stop},
+ {"start", udp_start},
+ {"stop", udp_stop},
{"connect", udp_connect},
- {"send", udp_send},
- {"recv", udp_recv},
+ {"send", udp_send},
+ {"recv", udp_recv},
{NULL, NULL}
};
luaL_setfuncs(L, udp_libs, 0);
luaL_newlib(L, udp_libs);
return 1;
-}
\ No newline at end of file
+}
diff --git a/luaclib/src/lz/Makefile b/luaclib/src/lz/Makefile
new file mode 100644
index 00000000..0c0e7353
--- /dev/null
+++ b/luaclib/src/lz/Makefile
@@ -0,0 +1,26 @@
+.PHONY : build rebuild clean
+
+default :
+ @echo "======================================="
+ @echo "Please use 'make build' command to build it.."
+ @echo "Please use 'make rebuild' command to build it.."
+ @echo "Please use 'make clean' command to clean all."
+ @echo "======================================="
+
+CC = cc
+
+INCLUDES += -I../../../src -I/usr/local/include
+LIBS = -L../ -L../../ -L../../../ -L/usr/local/lib
+
+CFLAGS = -O3 -Wall -shared -fPIC -Wl,-rpath,. -Wl,-rpath,.. -Wl,-rpath,/usr/local/lib
+
+# 是用内置库
+# MICRO =
+# DLL = -lcore
+# 使用`zlib`
+MICRO = -DUSE_ZLIB=1
+DLL = -lcore -lz
+
+build:
+ @$(CC) -o lz.so lzlib.c miniz.c $(INCLUDES) $(LIBS) $(CFLAGS) $(DLL) $(MICRO)
+ @mv *.so ../../
diff --git a/luaclib/src/lz/lzlib.c b/luaclib/src/lz/lzlib.c
new file mode 100644
index 00000000..9dbc8ddf
--- /dev/null
+++ b/luaclib/src/lz/lzlib.c
@@ -0,0 +1,256 @@
+/*
+** LICENSE: BSD
+** Author: CandyMi[https://github.com/candymi]
+*/
+#define LUA_LIB
+
+#include
+
+#if defined(USE_ZLIB)
+ #include
+#else
+ #define MINIZ_NO_MALLOC
+ #define MZ_FREE xrio_free
+ #define MZ_MALLOC xrio_malloc
+ #define MZ_REALLOC xrio_realloc
+ #include "miniz.h"
+#endif
+
+/* 分配内存 */
+#if defined(USE_ZLIB)
+void* stream_zalloc(void* opaque, unsigned items, unsigned nsize)
+#else
+static inline void set_int32(char data[4], uint32_t bit) {
+ data[0] = (uint8_t)(bit) & 0xff;
+ data[1] = (uint8_t)(bit >> 8) & 0xff;
+ data[2] = (uint8_t)(bit >> 16) & 0xff;
+ data[3] = (uint8_t)(bit >> 24) & 0xff;
+}
+void* stream_zalloc(void* opaque, size_t items, size_t nsize)
+#endif
+{
+ (void)opaque;
+ return xmalloc(((size_t)items) * ((size_t)nsize));
+}
+
+/* 释放内存 */
+void stream_free(void* opaque, void* ptr) {
+ (void)opaque;
+ xfree(ptr);
+}
+
+void stream_init(z_stream *z) {
+ memset(z, 0x0, sizeof(z_stream));
+ z->zalloc = stream_zalloc;
+ z->zfree = stream_free;
+}
+
+#if !defined(USE_ZLIB)
+/* | 1 | 1 | 1 | 1 | 4 | 1 | 1 |
+** ID1 + ID2 + CM + FLG + MTIME + XFL + OS
+** |2 + len| null + terminal | 2 byte |
+** FEXTRA + FNAME + FCOMMENT + FHCRC
+*/
+static inline size_t gzip_check(const uint8_t *buffer, size_t bsize) {
+ if (bsize <= 10 || memcmp(buffer, "\x1f\x8b\x08", 3))
+ return 0;
+
+ int flag = buffer[3];
+ size_t len = 10;
+ buffer += len;
+ // FEXTRA - 4 byte.
+ if (flag & 0x04) {
+ buffer += 2;
+ len += 4 + (buffer[0] | buffer[1] << 8);
+ }
+ // FNAME
+ if (flag & 0x08) {
+ while (*buffer++)
+ len++;
+ len += 1; /* NULL */
+ }
+ // FCOMMENT
+ if (flag & 0x10) {
+ while (*buffer++)
+ len++;
+ len += 1; /* NULL */
+ }
+ // FHCRC - 2 byte.
+ if (flag & 0x02)
+ len += 2;
+ return len;
+}
+#endif
+
+/* 压缩 */
+static inline int stream_deflate(lua_State* L, z_stream *z, int Z_MYFLUSH, int Z_MYMODE, const uint8_t* in, size_t in_size) {
+ if (!z)
+ return luaL_error(L, "[ZLIB ERROR]: `stream_deflate` got invalid `z_stream`.");
+
+ if (Z_OK != deflateInit2(z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, Z_MYMODE, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY))
+ return luaL_error(L, "[ZLIB ERROR]: `stream_deflate` init failed.");
+
+ /* 输入 */
+ z->next_in = (uint8_t *)in; z->avail_in = in_size;
+
+ /* 输出 */
+ size_t out_size = deflateBound(z, in_size) + MAX_WBITS;
+ uint8_t *out = lua_newuserdata(L, out_size);
+ z->next_out = out; z->avail_out = out_size;
+
+ /* 计算偏移值 */
+ int offset = 0;
+ if (Z_MYFLUSH == Z_SYNC_FLUSH)
+ offset = 4;
+
+ /* 压缩数据 */
+ deflate(z, Z_MYFLUSH);
+ deflateEnd(z);
+
+ /* 结束 */
+ lua_pushlstring(L, (const char*)out, z->total_out - offset);
+ lua_pushinteger(L, in_size);
+ return 2;
+}
+
+/* 解压 */
+static inline int stream_inflate(lua_State* L, z_stream *z, int Z_MYWIND, const uint8_t* in, size_t in_size) {
+ if (!z)
+ return luaL_error(L, "[ZLIB ERROR]: `stream_inflate` got Invalid `z_stream`.");
+
+ if (Z_OK != inflateInit2(z, Z_MYWIND))
+ return luaL_error(L, "[ZLIB ERROR]: `stream_inflate` init failed.");
+
+ /* 输入 */
+ z->next_in = (uint8_t *)in; z->avail_in = in_size;
+
+ /* 输出 */
+ luaL_Buffer B; luaL_buffinit(L, &B);
+ size_t bsize = 4096; uint8_t *buffer = alloca(bsize);
+
+ size_t offset = 0; /* z->total_out - offset 真正的输出缓冲区的长度 */
+ while (1)
+ {
+ z->next_out = buffer; z->avail_out = bsize;
+ int ret = inflate(z, Z_SYNC_FLUSH);
+ // printf("解压 : [ret] = %d, isize = [%ld], osize = [%ld]\n", ret, z->total_in, z->total_out);
+ if (ret != Z_OK && ret != Z_STREAM_END) {
+ inflateEnd(z);
+ lua_pushboolean(L, 0);
+ lua_pushfstring(L, "[ZLIB ERROR]: Invalid inflate buffer. %d", ret);
+ return 2;
+ }
+ luaL_addlstring(&B, (char *)buffer, z->total_out - offset);
+ // printf("buf[%s]\n", buffer);
+#if defined(USE_ZLIB)
+ if (z->total_in == in_size)
+ break;
+#else
+ if (z->total_out - offset < bsize)
+ break;
+#endif
+ offset = z->total_out;
+ }
+ /* 结束 */
+ inflateEnd(z);
+ luaL_pushresult(&B);
+ return 1;
+}
+
+/* RFC 7692 */
+int lws_compress(lua_State* L) {
+ z_stream z; stream_init(&z); size_t bsize;
+ const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize);
+ return stream_deflate(L, &z, Z_SYNC_FLUSH, -MAX_WBITS, buffer, bsize);
+}
+
+int lws_uncompress(lua_State* L) {
+ z_stream z; stream_init(&z); size_t bsize;
+ const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize);
+ return stream_inflate(L, &z, -MAX_WBITS, buffer, bsize);
+}
+
+/* RFC 1952 */
+int lgzip_compress(lua_State* L) {
+ z_stream z; stream_init(&z); size_t bsize;
+ const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize);
+#if defined(USE_ZLIB)
+ stream_deflate(L, &z, Z_FINISH, MAX_WBITS + 16, buffer, bsize);
+#else
+ // 计算文件长度 与 CRC32 校验
+ stream_deflate(L, &z, Z_FINISH, -MAX_WBITS, buffer, bsize);
+ size_t tsize; const char *text = lua_tolstring(L, -2, &tsize);
+ // 拷贝`GZIP`头部与压缩帧
+ char *data = lua_newuserdata(L, tsize + 18);
+ memcpy(data, "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x13", 10); memcpy(data + 10, text, tsize);
+ // 拷贝`CRC32`与`ISIZE`长度
+ set_int32(data + tsize + 10, crc32(0, buffer, bsize));
+ set_int32(data + tsize + 14, (uint32_t)bsize % 0xffffffff);
+ // `GZIP`包装完成
+ lua_pushlstring(L, data, tsize + 18); lua_pushinteger(L, bsize);
+#endif
+ return 2;
+}
+
+int lgzip_uncompress(lua_State* L) {
+ z_stream z; stream_init(&z); size_t bsize;
+ const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize);
+#if defined(USE_ZLIB)
+ return stream_inflate(L, &z, MAX_WBITS + 16, buffer, bsize);
+#else
+ size_t hlen = gzip_check(buffer, bsize);
+ if (!hlen || hlen >= bsize) {
+ lua_pushboolean(L, 0);
+ lua_pushliteral(L, "[ZLIB ERROR]: Invalid gzip header.");
+ return 2;
+ }
+ return stream_inflate(L, &z, -MAX_WBITS, buffer + hlen, bsize - hlen);
+#endif
+}
+
+/* RFC 1951 */
+int ldeflate_compress(lua_State* L) {
+ z_stream z; stream_init(&z); size_t bsize;
+ const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize);
+ return stream_deflate(L, &z, Z_FINISH, -MAX_WBITS, buffer, bsize);
+}
+
+int ldeflate_uncompress(lua_State* L) {
+ z_stream z; stream_init(&z); size_t bsize;
+ const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize);
+ return stream_inflate(L, &z, -MAX_WBITS, buffer, bsize);
+}
+
+/* RFC 1950 */
+int lzlib_compress(lua_State* L) {
+ z_stream z; stream_init(&z); size_t bsize;
+ const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize);
+ return stream_deflate(L, &z, Z_FINISH, MAX_WBITS, buffer, bsize);
+}
+
+int lzlib_uncompress(lua_State* L) {
+ z_stream z; stream_init(&z); size_t bsize;
+ const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize);
+ return stream_inflate(L, &z, MAX_WBITS, buffer, bsize);
+}
+
+LUAMOD_API int luaopen_lz(lua_State *L) {
+ luaL_checkversion(L);
+ luaL_Reg zlib_libs[] = {
+ /* LZ77压缩/解压方法 */
+ {"compress", ldeflate_compress},
+ {"uncompress", ldeflate_uncompress},
+ /* 原生压缩方法 */
+ {"compress2", lzlib_compress},
+ {"uncompress2", lzlib_uncompress},
+ /* gzip压缩/解压方法 */
+ {"gzcompress", lgzip_compress},
+ {"gzuncompress", lgzip_uncompress},
+ /* Websocket压缩/解压方法 */
+ {"wscompress", lws_compress},
+ {"wsuncompress", lws_uncompress},
+ {NULL, NULL}
+ };
+ luaL_newlib(L, zlib_libs);
+ return 1;
+}
\ No newline at end of file
diff --git a/luaclib/src/lz/miniz.c b/luaclib/src/lz/miniz.c
new file mode 100644
index 00000000..cbd8b7b2
--- /dev/null
+++ b/luaclib/src/lz/miniz.c
@@ -0,0 +1,7735 @@
+#include "miniz.h"
+/**************************************************************************
+ *
+ * Copyright 2013-2014 RAD Game Tools and Valve Software
+ * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ **************************************************************************/
+
+
+
+typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1];
+typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1];
+typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1];
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ------------------- zlib-style API's */
+
+mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len)
+{
+ mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16);
+ size_t block_len = buf_len % 5552;
+ if (!ptr)
+ return MZ_ADLER32_INIT;
+ while (buf_len)
+ {
+ for (i = 0; i + 7 < block_len; i += 8, ptr += 8)
+ {
+ s1 += ptr[0], s2 += s1;
+ s1 += ptr[1], s2 += s1;
+ s1 += ptr[2], s2 += s1;
+ s1 += ptr[3], s2 += s1;
+ s1 += ptr[4], s2 += s1;
+ s1 += ptr[5], s2 += s1;
+ s1 += ptr[6], s2 += s1;
+ s1 += ptr[7], s2 += s1;
+ }
+ for (; i < block_len; ++i)
+ s1 += *ptr++, s2 += s1;
+ s1 %= 65521U, s2 %= 65521U;
+ buf_len -= block_len;
+ block_len = 5552;
+ }
+ return (s2 << 16) + s1;
+}
+
+/* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */
+#if 0
+ mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)
+ {
+ static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
+ 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
+ mz_uint32 crcu32 = (mz_uint32)crc;
+ if (!ptr)
+ return MZ_CRC32_INIT;
+ crcu32 = ~crcu32;
+ while (buf_len--)
+ {
+ mz_uint8 b = *ptr++;
+ crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)];
+ crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)];
+ }
+ return ~crcu32;
+ }
+#elif defined(USE_EXTERNAL_MZCRC)
+/* If USE_EXTERNAL_CRC is defined, an external module will export the
+ * mz_crc32() symbol for us to use, e.g. an SSE-accelerated version.
+ * Depending on the impl, it may be necessary to ~ the input/output crc values.
+ */
+mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len);
+#else
+/* Faster, but larger CPU cache footprint.
+ */
+mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)
+{
+ static const mz_uint32 s_crc_table[256] =
+ {
+ 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535,
+ 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD,
+ 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D,
+ 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
+ 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
+ 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
+ 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC,
+ 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
+ 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,
+ 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
+ 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB,
+ 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
+ 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA,
+ 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE,
+ 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
+ 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
+ 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409,
+ 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
+ 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739,
+ 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
+ 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268,
+ 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0,
+ 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8,
+ 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
+ 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
+ 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703,
+ 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7,
+ 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
+ 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE,
+ 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
+ 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6,
+ 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
+ 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D,
+ 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5,
+ 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
+ 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
+ 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+ };
+
+ mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF;
+ const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr;
+
+ while (buf_len >= 4)
+ {
+ crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF];
+ crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF];
+ crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF];
+ crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF];
+ pByte_buf += 4;
+ buf_len -= 4;
+ }
+
+ while (buf_len)
+ {
+ crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF];
+ ++pByte_buf;
+ --buf_len;
+ }
+
+ return ~crc32;
+}
+#endif
+
+void mz_free(void *p)
+{
+ MZ_FREE(p);
+}
+
+MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size)
+{
+ (void)opaque, (void)items, (void)size;
+ return MZ_MALLOC(items * size);
+}
+MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address)
+{
+ (void)opaque, (void)address;
+ MZ_FREE(address);
+}
+MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size)
+{
+ (void)opaque, (void)address, (void)items, (void)size;
+ return MZ_REALLOC(address, items * size);
+}
+
+const char *mz_version(void)
+{
+ return MZ_VERSION;
+}
+
+#ifndef MINIZ_NO_ZLIB_APIS
+
+int mz_deflateInit(mz_streamp pStream, int level)
+{
+ return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY);
+}
+
+int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy)
+{
+ tdefl_compressor *pComp;
+ mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy);
+
+ if (!pStream)
+ return MZ_STREAM_ERROR;
+ if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)))
+ return MZ_PARAM_ERROR;
+
+ pStream->data_type = 0;
+ pStream->adler = MZ_ADLER32_INIT;
+ pStream->msg = NULL;
+ pStream->reserved = 0;
+ pStream->total_in = 0;
+ pStream->total_out = 0;
+ if (!pStream->zalloc)
+ pStream->zalloc = miniz_def_alloc_func;
+ if (!pStream->zfree)
+ pStream->zfree = miniz_def_free_func;
+
+ pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor));
+ if (!pComp)
+ return MZ_MEM_ERROR;
+
+ pStream->state = (struct mz_internal_state *)pComp;
+
+ if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY)
+ {
+ mz_deflateEnd(pStream);
+ return MZ_PARAM_ERROR;
+ }
+
+ return MZ_OK;
+}
+
+int mz_deflateReset(mz_streamp pStream)
+{
+ if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree))
+ return MZ_STREAM_ERROR;
+ pStream->total_in = pStream->total_out = 0;
+ tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags);
+ return MZ_OK;
+}
+
+int mz_deflate(mz_streamp pStream, int flush)
+{
+ size_t in_bytes, out_bytes;
+ mz_ulong orig_total_in, orig_total_out;
+ int mz_status = MZ_OK;
+
+ if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out))
+ return MZ_STREAM_ERROR;
+ if (!pStream->avail_out)
+ return MZ_BUF_ERROR;
+
+ if (flush == MZ_PARTIAL_FLUSH)
+ flush = MZ_SYNC_FLUSH;
+
+ if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE)
+ return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR;
+
+ orig_total_in = pStream->total_in;
+ orig_total_out = pStream->total_out;
+ for (;;)
+ {
+ tdefl_status defl_status;
+ in_bytes = pStream->avail_in;
+ out_bytes = pStream->avail_out;
+
+ defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush);
+ pStream->next_in += (mz_uint)in_bytes;
+ pStream->avail_in -= (mz_uint)in_bytes;
+ pStream->total_in += (mz_uint)in_bytes;
+ pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state);
+
+ pStream->next_out += (mz_uint)out_bytes;
+ pStream->avail_out -= (mz_uint)out_bytes;
+ pStream->total_out += (mz_uint)out_bytes;
+
+ if (defl_status < 0)
+ {
+ mz_status = MZ_STREAM_ERROR;
+ break;
+ }
+ else if (defl_status == TDEFL_STATUS_DONE)
+ {
+ mz_status = MZ_STREAM_END;
+ break;
+ }
+ else if (!pStream->avail_out)
+ break;
+ else if ((!pStream->avail_in) && (flush != MZ_FINISH))
+ {
+ if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out))
+ break;
+ return MZ_BUF_ERROR; /* Can't make forward progress without some input.
+ */
+ }
+ }
+ return mz_status;
+}
+
+int mz_deflateEnd(mz_streamp pStream)
+{
+ if (!pStream)
+ return MZ_STREAM_ERROR;
+ if (pStream->state)
+ {
+ pStream->zfree(pStream->opaque, pStream->state);
+ pStream->state = NULL;
+ }
+ return MZ_OK;
+}
+
+mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len)
+{
+ (void)pStream;
+ /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */
+ return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5);
+}
+
+int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level)
+{
+ int status;
+ mz_stream stream;
+ memset(&stream, 0, sizeof(stream));
+
+ /* In case mz_ulong is 64-bits (argh I hate longs). */
+ if ((source_len | *pDest_len) > 0xFFFFFFFFU)
+ return MZ_PARAM_ERROR;
+
+ stream.next_in = pSource;
+ stream.avail_in = (mz_uint32)source_len;
+ stream.next_out = pDest;
+ stream.avail_out = (mz_uint32)*pDest_len;
+
+ status = mz_deflateInit(&stream, level);
+ if (status != MZ_OK)
+ return status;
+
+ status = mz_deflate(&stream, MZ_FINISH);
+ if (status != MZ_STREAM_END)
+ {
+ mz_deflateEnd(&stream);
+ return (status == MZ_OK) ? MZ_BUF_ERROR : status;
+ }
+
+ *pDest_len = stream.total_out;
+ return mz_deflateEnd(&stream);
+}
+
+int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
+{
+ return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION);
+}
+
+mz_ulong mz_compressBound(mz_ulong source_len)
+{
+ return mz_deflateBound(NULL, source_len);
+}
+
+typedef struct
+{
+ tinfl_decompressor m_decomp;
+ mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed;
+ int m_window_bits;
+ mz_uint8 m_dict[TINFL_LZ_DICT_SIZE];
+ tinfl_status m_last_status;
+} inflate_state;
+
+int mz_inflateInit2(mz_streamp pStream, int window_bits)
+{
+ inflate_state *pDecomp;
+ if (!pStream)
+ return MZ_STREAM_ERROR;
+ if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))
+ return MZ_PARAM_ERROR;
+
+ pStream->data_type = 0;
+ pStream->adler = 0;
+ pStream->msg = NULL;
+ pStream->total_in = 0;
+ pStream->total_out = 0;
+ pStream->reserved = 0;
+ if (!pStream->zalloc)
+ pStream->zalloc = miniz_def_alloc_func;
+ if (!pStream->zfree)
+ pStream->zfree = miniz_def_free_func;
+
+ pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state));
+ if (!pDecomp)
+ return MZ_MEM_ERROR;
+
+ pStream->state = (struct mz_internal_state *)pDecomp;
+
+ tinfl_init(&pDecomp->m_decomp);
+ pDecomp->m_dict_ofs = 0;
+ pDecomp->m_dict_avail = 0;
+ pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;
+ pDecomp->m_first_call = 1;
+ pDecomp->m_has_flushed = 0;
+ pDecomp->m_window_bits = window_bits;
+
+ return MZ_OK;
+}
+
+int mz_inflateInit(mz_streamp pStream)
+{
+ return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS);
+}
+
+int mz_inflateReset(mz_streamp pStream)
+{
+ inflate_state *pDecomp;
+ if (!pStream)
+ return MZ_STREAM_ERROR;
+
+ pStream->data_type = 0;
+ pStream->adler = 0;
+ pStream->msg = NULL;
+ pStream->total_in = 0;
+ pStream->total_out = 0;
+ pStream->reserved = 0;
+
+ pDecomp = (inflate_state *)pStream->state;
+
+ tinfl_init(&pDecomp->m_decomp);
+ pDecomp->m_dict_ofs = 0;
+ pDecomp->m_dict_avail = 0;
+ pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;
+ pDecomp->m_first_call = 1;
+ pDecomp->m_has_flushed = 0;
+ /* pDecomp->m_window_bits = window_bits */;
+
+ return MZ_OK;
+}
+
+int mz_inflate(mz_streamp pStream, int flush)
+{
+ inflate_state *pState;
+ mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32;
+ size_t in_bytes, out_bytes, orig_avail_in;
+ tinfl_status status;
+
+ if ((!pStream) || (!pStream->state))
+ return MZ_STREAM_ERROR;
+ if (flush == MZ_PARTIAL_FLUSH)
+ flush = MZ_SYNC_FLUSH;
+ if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH))
+ return MZ_STREAM_ERROR;
+
+ pState = (inflate_state *)pStream->state;
+ if (pState->m_window_bits > 0)
+ decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER;
+ orig_avail_in = pStream->avail_in;
+
+ first_call = pState->m_first_call;
+ pState->m_first_call = 0;
+ if (pState->m_last_status < 0)
+ return MZ_DATA_ERROR;
+
+ if (pState->m_has_flushed && (flush != MZ_FINISH))
+ return MZ_STREAM_ERROR;
+ pState->m_has_flushed |= (flush == MZ_FINISH);
+
+ if ((flush == MZ_FINISH) && (first_call))
+ {
+ /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */
+ decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
+ in_bytes = pStream->avail_in;
+ out_bytes = pStream->avail_out;
+ status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags);
+ pState->m_last_status = status;
+ pStream->next_in += (mz_uint)in_bytes;
+ pStream->avail_in -= (mz_uint)in_bytes;
+ pStream->total_in += (mz_uint)in_bytes;
+ pStream->adler = tinfl_get_adler32(&pState->m_decomp);
+ pStream->next_out += (mz_uint)out_bytes;
+ pStream->avail_out -= (mz_uint)out_bytes;
+ pStream->total_out += (mz_uint)out_bytes;
+
+ if (status < 0)
+ return MZ_DATA_ERROR;
+ else if (status != TINFL_STATUS_DONE)
+ {
+ pState->m_last_status = TINFL_STATUS_FAILED;
+ return MZ_BUF_ERROR;
+ }
+ return MZ_STREAM_END;
+ }
+ /* flush != MZ_FINISH then we must assume there's more input. */
+ if (flush != MZ_FINISH)
+ decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT;
+
+ if (pState->m_dict_avail)
+ {
+ n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
+ memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
+ pStream->next_out += n;
+ pStream->avail_out -= n;
+ pStream->total_out += n;
+ pState->m_dict_avail -= n;
+ pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
+ return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
+ }
+
+ for (;;)
+ {
+ in_bytes = pStream->avail_in;
+ out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs;
+
+ status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags);
+ pState->m_last_status = status;
+
+ pStream->next_in += (mz_uint)in_bytes;
+ pStream->avail_in -= (mz_uint)in_bytes;
+ pStream->total_in += (mz_uint)in_bytes;
+ pStream->adler = tinfl_get_adler32(&pState->m_decomp);
+
+ pState->m_dict_avail = (mz_uint)out_bytes;
+
+ n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
+ memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
+ pStream->next_out += n;
+ pStream->avail_out -= n;
+ pStream->total_out += n;
+ pState->m_dict_avail -= n;
+ pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
+
+ if (status < 0)
+ return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */
+ else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in))
+ return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */
+ else if (flush == MZ_FINISH)
+ {
+ /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */
+ if (status == TINFL_STATUS_DONE)
+ return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END;
+ /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */
+ else if (!pStream->avail_out)
+ return MZ_BUF_ERROR;
+ }
+ else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail))
+ break;
+ }
+
+ return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
+}
+
+int mz_inflateEnd(mz_streamp pStream)
+{
+ if (!pStream)
+ return MZ_STREAM_ERROR;
+ if (pStream->state)
+ {
+ pStream->zfree(pStream->opaque, pStream->state);
+ pStream->state = NULL;
+ }
+ return MZ_OK;
+}
+int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len)
+{
+ mz_stream stream;
+ int status;
+ memset(&stream, 0, sizeof(stream));
+
+ /* In case mz_ulong is 64-bits (argh I hate longs). */
+ if ((*pSource_len | *pDest_len) > 0xFFFFFFFFU)
+ return MZ_PARAM_ERROR;
+
+ stream.next_in = pSource;
+ stream.avail_in = (mz_uint32)*pSource_len;
+ stream.next_out = pDest;
+ stream.avail_out = (mz_uint32)*pDest_len;
+
+ status = mz_inflateInit(&stream);
+ if (status != MZ_OK)
+ return status;
+
+ status = mz_inflate(&stream, MZ_FINISH);
+ *pSource_len = *pSource_len - stream.avail_in;
+ if (status != MZ_STREAM_END)
+ {
+ mz_inflateEnd(&stream);
+ return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status;
+ }
+ *pDest_len = stream.total_out;
+
+ return mz_inflateEnd(&stream);
+}
+
+int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
+{
+ return mz_uncompress2(pDest, pDest_len, pSource, &source_len);
+}
+
+const char *mz_error(int err)
+{
+ static struct
+ {
+ int m_err;
+ const char *m_pDesc;
+ } s_error_descs[] =
+ {
+ { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" }
+ };
+ mz_uint i;
+ for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i)
+ if (s_error_descs[i].m_err == err)
+ return s_error_descs[i].m_pDesc;
+ return NULL;
+}
+
+#endif /*MINIZ_NO_ZLIB_APIS */
+
+#ifdef __cplusplus
+}
+#endif
+
+/*
+ This is free and unencumbered software released into the public domain.
+
+ Anyone is free to copy, modify, publish, use, compile, sell, or
+ distribute this software, either in source code form or as a compiled
+ binary, for any purpose, commercial or non-commercial, and by any
+ means.
+
+ In jurisdictions that recognize copyright laws, the author or authors
+ of this software dedicate any and all copyright interest in the
+ software to the public domain. We make this dedication for the benefit
+ of the public at large and to the detriment of our heirs and
+ successors. We intend this dedication to be an overt act of
+ relinquishment in perpetuity of all present and future rights to this
+ software under copyright law.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+
+ For more information, please refer to
+*/
+/**************************************************************************
+ *
+ * Copyright 2013-2014 RAD Game Tools and Valve Software
+ * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ **************************************************************************/
+
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ------------------- Low-level Compression (independent from all decompression API's) */
+
+/* Purposely making these tables static for faster init and thread safety. */
+static const mz_uint16 s_tdefl_len_sym[256] =
+ {
+ 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272,
+ 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276,
+ 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,
+ 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280,
+ 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281,
+ 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282,
+ 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283,
+ 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285
+ };
+
+static const mz_uint8 s_tdefl_len_extra[256] =
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0
+ };
+
+static const mz_uint8 s_tdefl_small_dist_sym[512] =
+ {
+ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17
+ };
+
+static const mz_uint8 s_tdefl_small_dist_extra[512] =
+ {
+ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7
+ };
+
+static const mz_uint8 s_tdefl_large_dist_sym[128] =
+ {
+ 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
+ };
+
+static const mz_uint8 s_tdefl_large_dist_extra[128] =
+ {
+ 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13
+ };
+
+/* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */
+typedef struct
+{
+ mz_uint16 m_key, m_sym_index;
+} tdefl_sym_freq;
+static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1)
+{
+ mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2];
+ tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1;
+ MZ_CLEAR_OBJ(hist);
+ for (i = 0; i < num_syms; i++)
+ {
+ mz_uint freq = pSyms0[i].m_key;
+ hist[freq & 0xFF]++;
+ hist[256 + ((freq >> 8) & 0xFF)]++;
+ }
+ while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256]))
+ total_passes--;
+ for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8)
+ {
+ const mz_uint32 *pHist = &hist[pass << 8];
+ mz_uint offsets[256], cur_ofs = 0;
+ for (i = 0; i < 256; i++)
+ {
+ offsets[i] = cur_ofs;
+ cur_ofs += pHist[i];
+ }
+ for (i = 0; i < num_syms; i++)
+ pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i];
+ {
+ tdefl_sym_freq *t = pCur_syms;
+ pCur_syms = pNew_syms;
+ pNew_syms = t;
+ }
+ }
+ return pCur_syms;
+}
+
+/* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */
+static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n)
+{
+ int root, leaf, next, avbl, used, dpth;
+ if (n == 0)
+ return;
+ else if (n == 1)
+ {
+ A[0].m_key = 1;
+ return;
+ }
+ A[0].m_key += A[1].m_key;
+ root = 0;
+ leaf = 2;
+ for (next = 1; next < n - 1; next++)
+ {
+ if (leaf >= n || A[root].m_key < A[leaf].m_key)
+ {
+ A[next].m_key = A[root].m_key;
+ A[root++].m_key = (mz_uint16)next;
+ }
+ else
+ A[next].m_key = A[leaf++].m_key;
+ if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key))
+ {
+ A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key);
+ A[root++].m_key = (mz_uint16)next;
+ }
+ else
+ A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key);
+ }
+ A[n - 2].m_key = 0;
+ for (next = n - 3; next >= 0; next--)
+ A[next].m_key = A[A[next].m_key].m_key + 1;
+ avbl = 1;
+ used = dpth = 0;
+ root = n - 2;
+ next = n - 1;
+ while (avbl > 0)
+ {
+ while (root >= 0 && (int)A[root].m_key == dpth)
+ {
+ used++;
+ root--;
+ }
+ while (avbl > used)
+ {
+ A[next--].m_key = (mz_uint16)(dpth);
+ avbl--;
+ }
+ avbl = 2 * used;
+ dpth++;
+ used = 0;
+ }
+}
+
+/* Limits canonical Huffman code table's max code size. */
+enum
+{
+ TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32
+};
+static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size)
+{
+ int i;
+ mz_uint32 total = 0;
+ if (code_list_len <= 1)
+ return;
+ for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++)
+ pNum_codes[max_code_size] += pNum_codes[i];
+ for (i = max_code_size; i > 0; i--)
+ total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i));
+ while (total != (1UL << max_code_size))
+ {
+ pNum_codes[max_code_size]--;
+ for (i = max_code_size - 1; i > 0; i--)
+ if (pNum_codes[i])
+ {
+ pNum_codes[i]--;
+ pNum_codes[i + 1] += 2;
+ break;
+ }
+ total--;
+ }
+}
+
+static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table)
+{
+ int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE];
+ mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1];
+ MZ_CLEAR_OBJ(num_codes);
+ if (static_table)
+ {
+ for (i = 0; i < table_len; i++)
+ num_codes[d->m_huff_code_sizes[table_num][i]]++;
+ }
+ else
+ {
+ tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms;
+ int num_used_syms = 0;
+ const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0];
+ for (i = 0; i < table_len; i++)
+ if (pSym_count[i])
+ {
+ syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i];
+ syms0[num_used_syms++].m_sym_index = (mz_uint16)i;
+ }
+
+ pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1);
+ tdefl_calculate_minimum_redundancy(pSyms, num_used_syms);
+
+ for (i = 0; i < num_used_syms; i++)
+ num_codes[pSyms[i].m_key]++;
+
+ tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit);
+
+ MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]);
+ MZ_CLEAR_OBJ(d->m_huff_codes[table_num]);
+ for (i = 1, j = num_used_syms; i <= code_size_limit; i++)
+ for (l = num_codes[i]; l > 0; l--)
+ d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i);
+ }
+
+ next_code[1] = 0;
+ for (j = 0, i = 2; i <= code_size_limit; i++)
+ next_code[i] = j = ((j + num_codes[i - 1]) << 1);
+
+ for (i = 0; i < table_len; i++)
+ {
+ mz_uint rev_code = 0, code, code_size;
+ if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0)
+ continue;
+ code = next_code[code_size]++;
+ for (l = code_size; l > 0; l--, code >>= 1)
+ rev_code = (rev_code << 1) | (code & 1);
+ d->m_huff_codes[table_num][i] = (mz_uint16)rev_code;
+ }
+}
+
+#define TDEFL_PUT_BITS(b, l) \
+ do \
+ { \
+ mz_uint bits = b; \
+ mz_uint len = l; \
+ MZ_ASSERT(bits <= ((1U << len) - 1U)); \
+ d->m_bit_buffer |= (bits << d->m_bits_in); \
+ d->m_bits_in += len; \
+ while (d->m_bits_in >= 8) \
+ { \
+ if (d->m_pOutput_buf < d->m_pOutput_buf_end) \
+ *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \
+ d->m_bit_buffer >>= 8; \
+ d->m_bits_in -= 8; \
+ } \
+ } \
+ MZ_MACRO_END
+
+#define TDEFL_RLE_PREV_CODE_SIZE() \
+ { \
+ if (rle_repeat_count) \
+ { \
+ if (rle_repeat_count < 3) \
+ { \
+ d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \
+ while (rle_repeat_count--) \
+ packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \
+ } \
+ else \
+ { \
+ d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \
+ packed_code_sizes[num_packed_code_sizes++] = 16; \
+ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \
+ } \
+ rle_repeat_count = 0; \
+ } \
+ }
+
+#define TDEFL_RLE_ZERO_CODE_SIZE() \
+ { \
+ if (rle_z_count) \
+ { \
+ if (rle_z_count < 3) \
+ { \
+ d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \
+ while (rle_z_count--) \
+ packed_code_sizes[num_packed_code_sizes++] = 0; \
+ } \
+ else if (rle_z_count <= 10) \
+ { \
+ d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \
+ packed_code_sizes[num_packed_code_sizes++] = 17; \
+ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \
+ } \
+ else \
+ { \
+ d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \
+ packed_code_sizes[num_packed_code_sizes++] = 18; \
+ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \
+ } \
+ rle_z_count = 0; \
+ } \
+ }
+
+static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+
+static void tdefl_start_dynamic_block(tdefl_compressor *d)
+{
+ int num_lit_codes, num_dist_codes, num_bit_lengths;
+ mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index;
+ mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF;
+
+ d->m_huff_count[0][256] = 1;
+
+ tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE);
+ tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE);
+
+ for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--)
+ if (d->m_huff_code_sizes[0][num_lit_codes - 1])
+ break;
+ for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--)
+ if (d->m_huff_code_sizes[1][num_dist_codes - 1])
+ break;
+
+ memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes);
+ memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes);
+ total_code_sizes_to_pack = num_lit_codes + num_dist_codes;
+ num_packed_code_sizes = 0;
+ rle_z_count = 0;
+ rle_repeat_count = 0;
+
+ memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2);
+ for (i = 0; i < total_code_sizes_to_pack; i++)
+ {
+ mz_uint8 code_size = code_sizes_to_pack[i];
+ if (!code_size)
+ {
+ TDEFL_RLE_PREV_CODE_SIZE();
+ if (++rle_z_count == 138)
+ {
+ TDEFL_RLE_ZERO_CODE_SIZE();
+ }
+ }
+ else
+ {
+ TDEFL_RLE_ZERO_CODE_SIZE();
+ if (code_size != prev_code_size)
+ {
+ TDEFL_RLE_PREV_CODE_SIZE();
+ d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1);
+ packed_code_sizes[num_packed_code_sizes++] = code_size;
+ }
+ else if (++rle_repeat_count == 6)
+ {
+ TDEFL_RLE_PREV_CODE_SIZE();
+ }
+ }
+ prev_code_size = code_size;
+ }
+ if (rle_repeat_count)
+ {
+ TDEFL_RLE_PREV_CODE_SIZE();
+ }
+ else
+ {
+ TDEFL_RLE_ZERO_CODE_SIZE();
+ }
+
+ tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE);
+
+ TDEFL_PUT_BITS(2, 2);
+
+ TDEFL_PUT_BITS(num_lit_codes - 257, 5);
+ TDEFL_PUT_BITS(num_dist_codes - 1, 5);
+
+ for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--)
+ if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]])
+ break;
+ num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1));
+ TDEFL_PUT_BITS(num_bit_lengths - 4, 4);
+ for (i = 0; (int)i < num_bit_lengths; i++)
+ TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3);
+
+ for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;)
+ {
+ mz_uint code = packed_code_sizes[packed_code_sizes_index++];
+ MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2);
+ TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]);
+ if (code >= 16)
+ TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]);
+ }
+}
+
+static void tdefl_start_static_block(tdefl_compressor *d)
+{
+ mz_uint i;
+ mz_uint8 *p = &d->m_huff_code_sizes[0][0];
+
+ for (i = 0; i <= 143; ++i)
+ *p++ = 8;
+ for (; i <= 255; ++i)
+ *p++ = 9;
+ for (; i <= 279; ++i)
+ *p++ = 7;
+ for (; i <= 287; ++i)
+ *p++ = 8;
+
+ memset(d->m_huff_code_sizes[1], 5, 32);
+
+ tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE);
+ tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE);
+
+ TDEFL_PUT_BITS(1, 2);
+}
+
+static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
+
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS
+static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)
+{
+ mz_uint flags;
+ mz_uint8 *pLZ_codes;
+ mz_uint8 *pOutput_buf = d->m_pOutput_buf;
+ mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf;
+ mz_uint64 bit_buffer = d->m_bit_buffer;
+ mz_uint bits_in = d->m_bits_in;
+
+#define TDEFL_PUT_BITS_FAST(b, l) \
+ { \
+ bit_buffer |= (((mz_uint64)(b)) << bits_in); \
+ bits_in += (l); \
+ }
+
+ flags = 1;
+ for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1)
+ {
+ if (flags == 1)
+ flags = *pLZ_codes++ | 0x100;
+
+ if (flags & 1)
+ {
+ mz_uint s0, s1, n0, n1, sym, num_extra_bits;
+ mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1);
+ pLZ_codes += 3;
+
+ MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
+ TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
+ TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);
+
+ /* This sequence coaxes MSVC into using cmov's vs. jmp's. */
+ s0 = s_tdefl_small_dist_sym[match_dist & 511];
+ n0 = s_tdefl_small_dist_extra[match_dist & 511];
+ s1 = s_tdefl_large_dist_sym[match_dist >> 8];
+ n1 = s_tdefl_large_dist_extra[match_dist >> 8];
+ sym = (match_dist < 512) ? s0 : s1;
+ num_extra_bits = (match_dist < 512) ? n0 : n1;
+
+ MZ_ASSERT(d->m_huff_code_sizes[1][sym]);
+ TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);
+ TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);
+ }
+ else
+ {
+ mz_uint lit = *pLZ_codes++;
+ MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
+ TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
+
+ if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))
+ {
+ flags >>= 1;
+ lit = *pLZ_codes++;
+ MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
+ TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
+
+ if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))
+ {
+ flags >>= 1;
+ lit = *pLZ_codes++;
+ MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
+ TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
+ }
+ }
+ }
+
+ if (pOutput_buf >= d->m_pOutput_buf_end)
+ return MZ_FALSE;
+
+ *(mz_uint64 *)pOutput_buf = bit_buffer;
+ pOutput_buf += (bits_in >> 3);
+ bit_buffer >>= (bits_in & ~7);
+ bits_in &= 7;
+ }
+
+#undef TDEFL_PUT_BITS_FAST
+
+ d->m_pOutput_buf = pOutput_buf;
+ d->m_bits_in = 0;
+ d->m_bit_buffer = 0;
+
+ while (bits_in)
+ {
+ mz_uint32 n = MZ_MIN(bits_in, 16);
+ TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n);
+ bit_buffer >>= n;
+ bits_in -= n;
+ }
+
+ TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);
+
+ return (d->m_pOutput_buf < d->m_pOutput_buf_end);
+}
+#else
+static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)
+{
+ mz_uint flags;
+ mz_uint8 *pLZ_codes;
+
+ flags = 1;
+ for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1)
+ {
+ if (flags == 1)
+ flags = *pLZ_codes++ | 0x100;
+ if (flags & 1)
+ {
+ mz_uint sym, num_extra_bits;
+ mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8));
+ pLZ_codes += 3;
+
+ MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
+ TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
+ TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);
+
+ if (match_dist < 512)
+ {
+ sym = s_tdefl_small_dist_sym[match_dist];
+ num_extra_bits = s_tdefl_small_dist_extra[match_dist];
+ }
+ else
+ {
+ sym = s_tdefl_large_dist_sym[match_dist >> 8];
+ num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8];
+ }
+ MZ_ASSERT(d->m_huff_code_sizes[1][sym]);
+ TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);
+ TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);
+ }
+ else
+ {
+ mz_uint lit = *pLZ_codes++;
+ MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
+ TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
+ }
+ }
+
+ TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);
+
+ return (d->m_pOutput_buf < d->m_pOutput_buf_end);
+}
+#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */
+
+static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block)
+{
+ if (static_block)
+ tdefl_start_static_block(d);
+ else
+ tdefl_start_dynamic_block(d);
+ return tdefl_compress_lz_codes(d);
+}
+
+static int tdefl_flush_block(tdefl_compressor *d, int flush)
+{
+ mz_uint saved_bit_buf, saved_bits_in;
+ mz_uint8 *pSaved_output_buf;
+ mz_bool comp_block_succeeded = MZ_FALSE;
+ int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size;
+ mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf;
+
+ d->m_pOutput_buf = pOutput_buf_start;
+ d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16;
+
+ MZ_ASSERT(!d->m_output_flush_remaining);
+ d->m_output_flush_ofs = 0;
+ d->m_output_flush_remaining = 0;
+
+ *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left);
+ d->m_pLZ_code_buf -= (d->m_num_flags_left == 8);
+
+ if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index))
+ {
+ TDEFL_PUT_BITS(0x78, 8);
+ TDEFL_PUT_BITS(0x01, 8);
+ }
+
+ TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1);
+
+ pSaved_output_buf = d->m_pOutput_buf;
+ saved_bit_buf = d->m_bit_buffer;
+ saved_bits_in = d->m_bits_in;
+
+ if (!use_raw_block)
+ comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48));
+
+ /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */
+ if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) &&
+ ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size))
+ {
+ mz_uint i;
+ d->m_pOutput_buf = pSaved_output_buf;
+ d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;
+ TDEFL_PUT_BITS(0, 2);
+ if (d->m_bits_in)
+ {
+ TDEFL_PUT_BITS(0, 8 - d->m_bits_in);
+ }
+ for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF)
+ {
+ TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16);
+ }
+ for (i = 0; i < d->m_total_lz_bytes; ++i)
+ {
+ TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8);
+ }
+ }
+ /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */
+ else if (!comp_block_succeeded)
+ {
+ d->m_pOutput_buf = pSaved_output_buf;
+ d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;
+ tdefl_compress_block(d, MZ_TRUE);
+ }
+
+ if (flush)
+ {
+ if (flush == TDEFL_FINISH)
+ {
+ if (d->m_bits_in)
+ {
+ TDEFL_PUT_BITS(0, 8 - d->m_bits_in);
+ }
+ if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER)
+ {
+ mz_uint i, a = d->m_adler32;
+ for (i = 0; i < 4; i++)
+ {
+ TDEFL_PUT_BITS((a >> 24) & 0xFF, 8);
+ a <<= 8;
+ }
+ }
+ }
+ else
+ {
+ mz_uint i, z = 0;
+ TDEFL_PUT_BITS(0, 3);
+ if (d->m_bits_in)
+ {
+ TDEFL_PUT_BITS(0, 8 - d->m_bits_in);
+ }
+ for (i = 2; i; --i, z ^= 0xFFFF)
+ {
+ TDEFL_PUT_BITS(z & 0xFFFF, 16);
+ }
+ }
+ }
+
+ MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end);
+
+ memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
+ memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);
+
+ d->m_pLZ_code_buf = d->m_lz_code_buf + 1;
+ d->m_pLZ_flags = d->m_lz_code_buf;
+ d->m_num_flags_left = 8;
+ d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes;
+ d->m_total_lz_bytes = 0;
+ d->m_block_index++;
+
+ if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0)
+ {
+ if (d->m_pPut_buf_func)
+ {
+ *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;
+ if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user))
+ return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED);
+ }
+ else if (pOutput_buf_start == d->m_output_buf)
+ {
+ int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs));
+ memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy);
+ d->m_out_buf_ofs += bytes_to_copy;
+ if ((n -= bytes_to_copy) != 0)
+ {
+ d->m_output_flush_ofs = bytes_to_copy;
+ d->m_output_flush_remaining = n;
+ }
+ }
+ else
+ {
+ d->m_out_buf_ofs += n;
+ }
+ }
+
+ return d->m_output_flush_remaining;
+}
+
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
+#ifdef MINIZ_UNALIGNED_USE_MEMCPY
+static mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p)
+{
+ mz_uint16 ret;
+ memcpy(&ret, p, sizeof(mz_uint16));
+ return ret;
+}
+static mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p)
+{
+ mz_uint16 ret;
+ memcpy(&ret, p, sizeof(mz_uint16));
+ return ret;
+}
+#else
+#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p)
+#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p)
+#endif
+static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)
+{
+ mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;
+ mz_uint num_probes_left = d->m_max_probes[match_len >= 32];
+ const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q;
+ mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s);
+ MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN);
+ if (max_match_len <= match_len)
+ return;
+ for (;;)
+ {
+ for (;;)
+ {
+ if (--num_probes_left == 0)
+ return;
+#define TDEFL_PROBE \
+ next_probe_pos = d->m_next[probe_pos]; \
+ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \
+ return; \
+ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \
+ if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \
+ break;
+ TDEFL_PROBE;
+ TDEFL_PROBE;
+ TDEFL_PROBE;
+ }
+ if (!dist)
+ break;
+ q = (const mz_uint16 *)(d->m_dict + probe_pos);
+ if (TDEFL_READ_UNALIGNED_WORD2(q) != s01)
+ continue;
+ p = s;
+ probe_len = 32;
+ do
+ {
+ } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) &&
+ (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0));
+ if (!probe_len)
+ {
+ *pMatch_dist = dist;
+ *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN);
+ break;
+ }
+ else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len)
+ {
+ *pMatch_dist = dist;
+ if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len)
+ break;
+ c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]);
+ }
+ }
+}
+#else
+static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)
+{
+ mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;
+ mz_uint num_probes_left = d->m_max_probes[match_len >= 32];
+ const mz_uint8 *s = d->m_dict + pos, *p, *q;
+ mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1];
+ MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN);
+ if (max_match_len <= match_len)
+ return;
+ for (;;)
+ {
+ for (;;)
+ {
+ if (--num_probes_left == 0)
+ return;
+#define TDEFL_PROBE \
+ next_probe_pos = d->m_next[probe_pos]; \
+ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \
+ return; \
+ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \
+ if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \
+ break;
+ TDEFL_PROBE;
+ TDEFL_PROBE;
+ TDEFL_PROBE;
+ }
+ if (!dist)
+ break;
+ p = s;
+ q = d->m_dict + probe_pos;
+ for (probe_len = 0; probe_len < max_match_len; probe_len++)
+ if (*p++ != *q++)
+ break;
+ if (probe_len > match_len)
+ {
+ *pMatch_dist = dist;
+ if ((*pMatch_len = match_len = probe_len) == max_match_len)
+ return;
+ c0 = d->m_dict[pos + match_len];
+ c1 = d->m_dict[pos + match_len - 1];
+ }
+ }
+}
+#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */
+
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
+#ifdef MINIZ_UNALIGNED_USE_MEMCPY
+static mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8* p)
+{
+ mz_uint32 ret;
+ memcpy(&ret, p, sizeof(mz_uint32));
+ return ret;
+}
+#else
+#define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p)
+#endif
+static mz_bool tdefl_compress_fast(tdefl_compressor *d)
+{
+ /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */
+ mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left;
+ mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags;
+ mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;
+
+ while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size)))
+ {
+ const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096;
+ mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;
+ mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size);
+ d->m_src_buf_left -= num_bytes_to_process;
+ lookahead_size += num_bytes_to_process;
+
+ while (num_bytes_to_process)
+ {
+ mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process);
+ memcpy(d->m_dict + dst_pos, d->m_pSrc, n);
+ if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
+ memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos));
+ d->m_pSrc += n;
+ dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK;
+ num_bytes_to_process -= n;
+ }
+
+ dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size);
+ if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE))
+ break;
+
+ while (lookahead_size >= 4)
+ {
+ mz_uint cur_match_dist, cur_match_len = 1;
+ mz_uint8 *pCur_dict = d->m_dict + cur_pos;
+ mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF;
+ mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK;
+ mz_uint probe_pos = d->m_hash[hash];
+ d->m_hash[hash] = (mz_uint16)lookahead_pos;
+
+ if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((TDEFL_READ_UNALIGNED_WORD32(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram))
+ {
+ const mz_uint16 *p = (const mz_uint16 *)pCur_dict;
+ const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos);
+ mz_uint32 probe_len = 32;
+ do
+ {
+ } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) &&
+ (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0));
+ cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q);
+ if (!probe_len)
+ cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0;
+
+ if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)))
+ {
+ cur_match_len = 1;
+ *pLZ_code_buf++ = (mz_uint8)first_trigram;
+ *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
+ d->m_huff_count[0][(mz_uint8)first_trigram]++;
+ }
+ else
+ {
+ mz_uint32 s0, s1;
+ cur_match_len = MZ_MIN(cur_match_len, lookahead_size);
+
+ MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE));
+
+ cur_match_dist--;
+
+ pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN);
+#ifdef MINIZ_UNALIGNED_USE_MEMCPY
+ memcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist));
+#else
+ *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist;
+#endif
+ pLZ_code_buf += 3;
+ *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80);
+
+ s0 = s_tdefl_small_dist_sym[cur_match_dist & 511];
+ s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8];
+ d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++;
+
+ d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++;
+ }
+ }
+ else
+ {
+ *pLZ_code_buf++ = (mz_uint8)first_trigram;
+ *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
+ d->m_huff_count[0][(mz_uint8)first_trigram]++;
+ }
+
+ if (--num_flags_left == 0)
+ {
+ num_flags_left = 8;
+ pLZ_flags = pLZ_code_buf++;
+ }
+
+ total_lz_bytes += cur_match_len;
+ lookahead_pos += cur_match_len;
+ dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE);
+ cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK;
+ MZ_ASSERT(lookahead_size >= cur_match_len);
+ lookahead_size -= cur_match_len;
+
+ if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])
+ {
+ int n;
+ d->m_lookahead_pos = lookahead_pos;
+ d->m_lookahead_size = lookahead_size;
+ d->m_dict_size = dict_size;
+ d->m_total_lz_bytes = total_lz_bytes;
+ d->m_pLZ_code_buf = pLZ_code_buf;
+ d->m_pLZ_flags = pLZ_flags;
+ d->m_num_flags_left = num_flags_left;
+ if ((n = tdefl_flush_block(d, 0)) != 0)
+ return (n < 0) ? MZ_FALSE : MZ_TRUE;
+ total_lz_bytes = d->m_total_lz_bytes;
+ pLZ_code_buf = d->m_pLZ_code_buf;
+ pLZ_flags = d->m_pLZ_flags;
+ num_flags_left = d->m_num_flags_left;
+ }
+ }
+
+ while (lookahead_size)
+ {
+ mz_uint8 lit = d->m_dict[cur_pos];
+
+ total_lz_bytes++;
+ *pLZ_code_buf++ = lit;
+ *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
+ if (--num_flags_left == 0)
+ {
+ num_flags_left = 8;
+ pLZ_flags = pLZ_code_buf++;
+ }
+
+ d->m_huff_count[0][lit]++;
+
+ lookahead_pos++;
+ dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE);
+ cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK;
+ lookahead_size--;
+
+ if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])
+ {
+ int n;
+ d->m_lookahead_pos = lookahead_pos;
+ d->m_lookahead_size = lookahead_size;
+ d->m_dict_size = dict_size;
+ d->m_total_lz_bytes = total_lz_bytes;
+ d->m_pLZ_code_buf = pLZ_code_buf;
+ d->m_pLZ_flags = pLZ_flags;
+ d->m_num_flags_left = num_flags_left;
+ if ((n = tdefl_flush_block(d, 0)) != 0)
+ return (n < 0) ? MZ_FALSE : MZ_TRUE;
+ total_lz_bytes = d->m_total_lz_bytes;
+ pLZ_code_buf = d->m_pLZ_code_buf;
+ pLZ_flags = d->m_pLZ_flags;
+ num_flags_left = d->m_num_flags_left;
+ }
+ }
+ }
+
+ d->m_lookahead_pos = lookahead_pos;
+ d->m_lookahead_size = lookahead_size;
+ d->m_dict_size = dict_size;
+ d->m_total_lz_bytes = total_lz_bytes;
+ d->m_pLZ_code_buf = pLZ_code_buf;
+ d->m_pLZ_flags = pLZ_flags;
+ d->m_num_flags_left = num_flags_left;
+ return MZ_TRUE;
+}
+#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */
+
+static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit)
+{
+ d->m_total_lz_bytes++;
+ *d->m_pLZ_code_buf++ = lit;
+ *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1);
+ if (--d->m_num_flags_left == 0)
+ {
+ d->m_num_flags_left = 8;
+ d->m_pLZ_flags = d->m_pLZ_code_buf++;
+ }
+ d->m_huff_count[0][lit]++;
+}
+
+static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist)
+{
+ mz_uint32 s0, s1;
+
+ MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE));
+
+ d->m_total_lz_bytes += match_len;
+
+ d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN);
+
+ match_dist -= 1;
+ d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF);
+ d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8);
+ d->m_pLZ_code_buf += 3;
+
+ *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80);
+ if (--d->m_num_flags_left == 0)
+ {
+ d->m_num_flags_left = 8;
+ d->m_pLZ_flags = d->m_pLZ_code_buf++;
+ }
+
+ s0 = s_tdefl_small_dist_sym[match_dist & 511];
+ s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127];
+ d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++;
+ d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++;
+}
+
+static mz_bool tdefl_compress_normal(tdefl_compressor *d)
+{
+ const mz_uint8 *pSrc = d->m_pSrc;
+ size_t src_buf_left = d->m_src_buf_left;
+ tdefl_flush flush = d->m_flush;
+
+ while ((src_buf_left) || ((flush) && (d->m_lookahead_size)))
+ {
+ mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos;
+ /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */
+ if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1))
+ {
+ mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2;
+ mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK];
+ mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size);
+ const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process;
+ src_buf_left -= num_bytes_to_process;
+ d->m_lookahead_size += num_bytes_to_process;
+ while (pSrc != pSrc_end)
+ {
+ mz_uint8 c = *pSrc++;
+ d->m_dict[dst_pos] = c;
+ if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
+ d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;
+ hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);
+ d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash];
+ d->m_hash[hash] = (mz_uint16)(ins_pos);
+ dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK;
+ ins_pos++;
+ }
+ }
+ else
+ {
+ while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))
+ {
+ mz_uint8 c = *pSrc++;
+ mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;
+ src_buf_left--;
+ d->m_dict[dst_pos] = c;
+ if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
+ d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;
+ if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN)
+ {
+ mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2;
+ mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);
+ d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash];
+ d->m_hash[hash] = (mz_uint16)(ins_pos);
+ }
+ }
+ }
+ d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size);
+ if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))
+ break;
+
+ /* Simple lazy/greedy parsing state machine. */
+ len_to_move = 1;
+ cur_match_dist = 0;
+ cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1);
+ cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;
+ if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS))
+ {
+ if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))
+ {
+ mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK];
+ cur_match_len = 0;
+ while (cur_match_len < d->m_lookahead_size)
+ {
+ if (d->m_dict[cur_pos + cur_match_len] != c)
+ break;
+ cur_match_len++;
+ }
+ if (cur_match_len < TDEFL_MIN_MATCH_LEN)
+ cur_match_len = 0;
+ else
+ cur_match_dist = 1;
+ }
+ }
+ else
+ {
+ tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len);
+ }
+ if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5)))
+ {
+ cur_match_dist = cur_match_len = 0;
+ }
+ if (d->m_saved_match_len)
+ {
+ if (cur_match_len > d->m_saved_match_len)
+ {
+ tdefl_record_literal(d, (mz_uint8)d->m_saved_lit);
+ if (cur_match_len >= 128)
+ {
+ tdefl_record_match(d, cur_match_len, cur_match_dist);
+ d->m_saved_match_len = 0;
+ len_to_move = cur_match_len;
+ }
+ else
+ {
+ d->m_saved_lit = d->m_dict[cur_pos];
+ d->m_saved_match_dist = cur_match_dist;
+ d->m_saved_match_len = cur_match_len;
+ }
+ }
+ else
+ {
+ tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist);
+ len_to_move = d->m_saved_match_len - 1;
+ d->m_saved_match_len = 0;
+ }
+ }
+ else if (!cur_match_dist)
+ tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]);
+ else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128))
+ {
+ tdefl_record_match(d, cur_match_len, cur_match_dist);
+ len_to_move = cur_match_len;
+ }
+ else
+ {
+ d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)];
+ d->m_saved_match_dist = cur_match_dist;
+ d->m_saved_match_len = cur_match_len;
+ }
+ /* Move the lookahead forward by len_to_move bytes. */
+ d->m_lookahead_pos += len_to_move;
+ MZ_ASSERT(d->m_lookahead_size >= len_to_move);
+ d->m_lookahead_size -= len_to_move;
+ d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE);
+ /* Check if it's time to flush the current LZ codes to the internal output buffer. */
+ if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) ||
+ ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))))
+ {
+ int n;
+ d->m_pSrc = pSrc;
+ d->m_src_buf_left = src_buf_left;
+ if ((n = tdefl_flush_block(d, 0)) != 0)
+ return (n < 0) ? MZ_FALSE : MZ_TRUE;
+ }
+ }
+
+ d->m_pSrc = pSrc;
+ d->m_src_buf_left = src_buf_left;
+ return MZ_TRUE;
+}
+
+static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d)
+{
+ if (d->m_pIn_buf_size)
+ {
+ *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;
+ }
+
+ if (d->m_pOut_buf_size)
+ {
+ size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining);
+ memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n);
+ d->m_output_flush_ofs += (mz_uint)n;
+ d->m_output_flush_remaining -= (mz_uint)n;
+ d->m_out_buf_ofs += n;
+
+ *d->m_pOut_buf_size = d->m_out_buf_ofs;
+ }
+
+ return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY;
+}
+
+tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush)
+{
+ if (!d)
+ {
+ if (pIn_buf_size)
+ *pIn_buf_size = 0;
+ if (pOut_buf_size)
+ *pOut_buf_size = 0;
+ return TDEFL_STATUS_BAD_PARAM;
+ }
+
+ d->m_pIn_buf = pIn_buf;
+ d->m_pIn_buf_size = pIn_buf_size;
+ d->m_pOut_buf = pOut_buf;
+ d->m_pOut_buf_size = pOut_buf_size;
+ d->m_pSrc = (const mz_uint8 *)(pIn_buf);
+ d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0;
+ d->m_out_buf_ofs = 0;
+ d->m_flush = flush;
+
+ if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) ||
+ (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf))
+ {
+ if (pIn_buf_size)
+ *pIn_buf_size = 0;
+ if (pOut_buf_size)
+ *pOut_buf_size = 0;
+ return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM);
+ }
+ d->m_wants_to_finish |= (flush == TDEFL_FINISH);
+
+ if ((d->m_output_flush_remaining) || (d->m_finished))
+ return (d->m_prev_return_status = tdefl_flush_output_buffer(d));
+
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
+ if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) &&
+ ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) &&
+ ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0))
+ {
+ if (!tdefl_compress_fast(d))
+ return d->m_prev_return_status;
+ }
+ else
+#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */
+ {
+ if (!tdefl_compress_normal(d))
+ return d->m_prev_return_status;
+ }
+
+ if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf))
+ d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf);
+
+ if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining))
+ {
+ if (tdefl_flush_block(d, flush) < 0)
+ return d->m_prev_return_status;
+ d->m_finished = (flush == TDEFL_FINISH);
+ if (flush == TDEFL_FULL_FLUSH)
+ {
+ MZ_CLEAR_OBJ(d->m_hash);
+ MZ_CLEAR_OBJ(d->m_next);
+ d->m_dict_size = 0;
+ }
+ }
+
+ return (d->m_prev_return_status = tdefl_flush_output_buffer(d));
+}
+
+tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush)
+{
+ MZ_ASSERT(d->m_pPut_buf_func);
+ return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush);
+}
+
+tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
+{
+ d->m_pPut_buf_func = pPut_buf_func;
+ d->m_pPut_buf_user = pPut_buf_user;
+ d->m_flags = (mz_uint)(flags);
+ d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3;
+ d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0;
+ d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3;
+ if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG))
+ MZ_CLEAR_OBJ(d->m_hash);
+ d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0;
+ d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0;
+ d->m_pLZ_code_buf = d->m_lz_code_buf + 1;
+ d->m_pLZ_flags = d->m_lz_code_buf;
+ *d->m_pLZ_flags = 0;
+ d->m_num_flags_left = 8;
+ d->m_pOutput_buf = d->m_output_buf;
+ d->m_pOutput_buf_end = d->m_output_buf;
+ d->m_prev_return_status = TDEFL_STATUS_OKAY;
+ d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0;
+ d->m_adler32 = 1;
+ d->m_pIn_buf = NULL;
+ d->m_pOut_buf = NULL;
+ d->m_pIn_buf_size = NULL;
+ d->m_pOut_buf_size = NULL;
+ d->m_flush = TDEFL_NO_FLUSH;
+ d->m_pSrc = NULL;
+ d->m_src_buf_left = 0;
+ d->m_out_buf_ofs = 0;
+ if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG))
+ MZ_CLEAR_OBJ(d->m_dict);
+ memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
+ memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);
+ return TDEFL_STATUS_OKAY;
+}
+
+tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d)
+{
+ return d->m_prev_return_status;
+}
+
+mz_uint32 tdefl_get_adler32(tdefl_compressor *d)
+{
+ return d->m_adler32;
+}
+
+mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
+{
+ tdefl_compressor *pComp;
+ mz_bool succeeded;
+ if (((buf_len) && (!pBuf)) || (!pPut_buf_func))
+ return MZ_FALSE;
+ pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));
+ if (!pComp)
+ return MZ_FALSE;
+ succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY);
+ succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE);
+ MZ_FREE(pComp);
+ return succeeded;
+}
+
+typedef struct
+{
+ size_t m_size, m_capacity;
+ mz_uint8 *m_pBuf;
+ mz_bool m_expandable;
+} tdefl_output_buffer;
+
+static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser)
+{
+ tdefl_output_buffer *p = (tdefl_output_buffer *)pUser;
+ size_t new_size = p->m_size + len;
+ if (new_size > p->m_capacity)
+ {
+ size_t new_capacity = p->m_capacity;
+ mz_uint8 *pNew_buf;
+ if (!p->m_expandable)
+ return MZ_FALSE;
+ do
+ {
+ new_capacity = MZ_MAX(128U, new_capacity << 1U);
+ } while (new_size > new_capacity);
+ pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity);
+ if (!pNew_buf)
+ return MZ_FALSE;
+ p->m_pBuf = pNew_buf;
+ p->m_capacity = new_capacity;
+ }
+ memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len);
+ p->m_size = new_size;
+ return MZ_TRUE;
+}
+
+void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)
+{
+ tdefl_output_buffer out_buf;
+ MZ_CLEAR_OBJ(out_buf);
+ if (!pOut_len)
+ return MZ_FALSE;
+ else
+ *pOut_len = 0;
+ out_buf.m_expandable = MZ_TRUE;
+ if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags))
+ return NULL;
+ *pOut_len = out_buf.m_size;
+ return out_buf.m_pBuf;
+}
+
+size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)
+{
+ tdefl_output_buffer out_buf;
+ MZ_CLEAR_OBJ(out_buf);
+ if (!pOut_buf)
+ return 0;
+ out_buf.m_pBuf = (mz_uint8 *)pOut_buf;
+ out_buf.m_capacity = out_buf_len;
+ if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags))
+ return 0;
+ return out_buf.m_size;
+}
+
+static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };
+
+/* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */
+mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy)
+{
+ mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0);
+ if (window_bits > 0)
+ comp_flags |= TDEFL_WRITE_ZLIB_HEADER;
+
+ if (!level)
+ comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS;
+ else if (strategy == MZ_FILTERED)
+ comp_flags |= TDEFL_FILTER_MATCHES;
+ else if (strategy == MZ_HUFFMAN_ONLY)
+ comp_flags &= ~TDEFL_MAX_PROBES_MASK;
+ else if (strategy == MZ_FIXED)
+ comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS;
+ else if (strategy == MZ_RLE)
+ comp_flags |= TDEFL_RLE_MATCHES;
+
+ return comp_flags;
+}
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */
+#endif
+
+/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at
+ http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/.
+ This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */
+void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip)
+{
+ /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */
+ static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };
+ tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));
+ tdefl_output_buffer out_buf;
+ int i, bpl = w * num_chans, y, z;
+ mz_uint32 c;
+ *pLen_out = 0;
+ if (!pComp)
+ return NULL;
+ MZ_CLEAR_OBJ(out_buf);
+ out_buf.m_expandable = MZ_TRUE;
+ out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h);
+ if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity)))
+ {
+ MZ_FREE(pComp);
+ return NULL;
+ }
+ /* write dummy header */
+ for (z = 41; z; --z)
+ tdefl_output_buffer_putter(&z, 1, &out_buf);
+ /* compress image data */
+ tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER);
+ for (y = 0; y < h; ++y)
+ {
+ tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH);
+ tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH);
+ }
+ if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE)
+ {
+ MZ_FREE(pComp);
+ MZ_FREE(out_buf.m_pBuf);
+ return NULL;
+ }
+ /* write real header */
+ *pLen_out = out_buf.m_size - 41;
+ {
+ static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 };
+ mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d,
+ 0x0a, 0x1a, 0x0a, 0x00, 0x00,
+ 0x00, 0x0d, 0x49, 0x48, 0x44,
+ 0x52, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x49, 0x44, 0x41,
+ 0x54 };
+ pnghdr[18] = (mz_uint8)(w >> 8);
+ pnghdr[19] = (mz_uint8)w;
+ pnghdr[22] = (mz_uint8)(h >> 8);
+ pnghdr[23] = (mz_uint8)h;
+ pnghdr[25] = chans[num_chans];
+ pnghdr[33] = (mz_uint8)(*pLen_out >> 24);
+ pnghdr[34] = (mz_uint8)(*pLen_out >> 16);
+ pnghdr[35] = (mz_uint8)(*pLen_out >> 8);
+ pnghdr[36] = (mz_uint8)*pLen_out;
+ c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17);
+ for (i = 0; i < 4; ++i, c <<= 8)
+ ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24);
+ memcpy(out_buf.m_pBuf, pnghdr, 41);
+ }
+ /* write footer (IDAT CRC-32, followed by IEND chunk) */
+ if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf))
+ {
+ *pLen_out = 0;
+ MZ_FREE(pComp);
+ MZ_FREE(out_buf.m_pBuf);
+ return NULL;
+ }
+ c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4);
+ for (i = 0; i < 4; ++i, c <<= 8)
+ (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24);
+ /* compute final size of file, grab compressed data buffer and return */
+ *pLen_out += 57;
+ MZ_FREE(pComp);
+ return out_buf.m_pBuf;
+}
+void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out)
+{
+ /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */
+ return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE);
+}
+
+#ifndef MINIZ_NO_MALLOC
+/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */
+/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */
+/* structure size and allocation mechanism. */
+tdefl_compressor *tdefl_compressor_alloc()
+{
+ return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));
+}
+
+void tdefl_compressor_free(tdefl_compressor *pComp)
+{
+ MZ_FREE(pComp);
+}
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+ /**************************************************************************
+ *
+ * Copyright 2013-2014 RAD Game Tools and Valve Software
+ * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ **************************************************************************/
+
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ------------------- Low-level Decompression (completely independent from all compression API's) */
+
+#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l)
+#define TINFL_MEMSET(p, c, l) memset(p, c, l)
+
+#define TINFL_CR_BEGIN \
+ switch (r->m_state) \
+ { \
+ case 0:
+#define TINFL_CR_RETURN(state_index, result) \
+ do \
+ { \
+ status = result; \
+ r->m_state = state_index; \
+ goto common_exit; \
+ case state_index:; \
+ } \
+ MZ_MACRO_END
+#define TINFL_CR_RETURN_FOREVER(state_index, result) \
+ do \
+ { \
+ for (;;) \
+ { \
+ TINFL_CR_RETURN(state_index, result); \
+ } \
+ } \
+ MZ_MACRO_END
+#define TINFL_CR_FINISH }
+
+#define TINFL_GET_BYTE(state_index, c) \
+ do \
+ { \
+ while (pIn_buf_cur >= pIn_buf_end) \
+ { \
+ TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \
+ } \
+ c = *pIn_buf_cur++; \
+ } \
+ MZ_MACRO_END
+
+#define TINFL_NEED_BITS(state_index, n) \
+ do \
+ { \
+ mz_uint c; \
+ TINFL_GET_BYTE(state_index, c); \
+ bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \
+ num_bits += 8; \
+ } while (num_bits < (mz_uint)(n))
+#define TINFL_SKIP_BITS(state_index, n) \
+ do \
+ { \
+ if (num_bits < (mz_uint)(n)) \
+ { \
+ TINFL_NEED_BITS(state_index, n); \
+ } \
+ bit_buf >>= (n); \
+ num_bits -= (n); \
+ } \
+ MZ_MACRO_END
+#define TINFL_GET_BITS(state_index, b, n) \
+ do \
+ { \
+ if (num_bits < (mz_uint)(n)) \
+ { \
+ TINFL_NEED_BITS(state_index, n); \
+ } \
+ b = bit_buf & ((1 << (n)) - 1); \
+ bit_buf >>= (n); \
+ num_bits -= (n); \
+ } \
+ MZ_MACRO_END
+
+/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */
+/* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */
+/* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */
+/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */
+#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \
+ do \
+ { \
+ temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \
+ if (temp >= 0) \
+ { \
+ code_len = temp >> 9; \
+ if ((code_len) && (num_bits >= code_len)) \
+ break; \
+ } \
+ else if (num_bits > TINFL_FAST_LOOKUP_BITS) \
+ { \
+ code_len = TINFL_FAST_LOOKUP_BITS; \
+ do \
+ { \
+ temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \
+ } while ((temp < 0) && (num_bits >= (code_len + 1))); \
+ if (temp >= 0) \
+ break; \
+ } \
+ TINFL_GET_BYTE(state_index, c); \
+ bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \
+ num_bits += 8; \
+ } while (num_bits < 15);
+
+/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */
+/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */
+/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */
+/* The slow path is only executed at the very end of the input buffer. */
+/* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */
+/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */
+#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \
+ do \
+ { \
+ int temp; \
+ mz_uint code_len, c; \
+ if (num_bits < 15) \
+ { \
+ if ((pIn_buf_end - pIn_buf_cur) < 2) \
+ { \
+ TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \
+ } \
+ else \
+ { \
+ bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \
+ pIn_buf_cur += 2; \
+ num_bits += 16; \
+ } \
+ } \
+ if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \
+ code_len = temp >> 9, temp &= 511; \
+ else \
+ { \
+ code_len = TINFL_FAST_LOOKUP_BITS; \
+ do \
+ { \
+ temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \
+ } while (temp < 0); \
+ } \
+ sym = temp; \
+ bit_buf >>= code_len; \
+ num_bits -= code_len; \
+ } \
+ MZ_MACRO_END
+
+tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags)
+{
+ static const int s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 };
+ static const int s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 };
+ static const int s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 };
+ static const int s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 };
+ static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+ static const int s_min_table_sizes[3] = { 257, 1, 4 };
+
+ tinfl_status status = TINFL_STATUS_FAILED;
+ mz_uint32 num_bits, dist, counter, num_extra;
+ tinfl_bit_buf_t bit_buf;
+ const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size;
+ mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size;
+ size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start;
+
+ /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */
+ if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start))
+ {
+ *pIn_buf_size = *pOut_buf_size = 0;
+ return TINFL_STATUS_BAD_PARAM;
+ }
+
+ num_bits = r->m_num_bits;
+ bit_buf = r->m_bit_buf;
+ dist = r->m_dist;
+ counter = r->m_counter;
+ num_extra = r->m_num_extra;
+ dist_from_out_buf_start = r->m_dist_from_out_buf_start;
+ TINFL_CR_BEGIN
+
+ bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0;
+ r->m_z_adler32 = r->m_check_adler32 = 1;
+ if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
+ {
+ TINFL_GET_BYTE(1, r->m_zhdr0);
+ TINFL_GET_BYTE(2, r->m_zhdr1);
+ counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8));
+ if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))
+ counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4)))));
+ if (counter)
+ {
+ TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED);
+ }
+ }
+
+ do
+ {
+ TINFL_GET_BITS(3, r->m_final, 3);
+ r->m_type = r->m_final >> 1;
+ if (r->m_type == 0)
+ {
+ TINFL_SKIP_BITS(5, num_bits & 7);
+ for (counter = 0; counter < 4; ++counter)
+ {
+ if (num_bits)
+ TINFL_GET_BITS(6, r->m_raw_header[counter], 8);
+ else
+ TINFL_GET_BYTE(7, r->m_raw_header[counter]);
+ }
+ if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8))))
+ {
+ TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED);
+ }
+ while ((counter) && (num_bits))
+ {
+ TINFL_GET_BITS(51, dist, 8);
+ while (pOut_buf_cur >= pOut_buf_end)
+ {
+ TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT);
+ }
+ *pOut_buf_cur++ = (mz_uint8)dist;
+ counter--;
+ }
+ while (counter)
+ {
+ size_t n;
+ while (pOut_buf_cur >= pOut_buf_end)
+ {
+ TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT);
+ }
+ while (pIn_buf_cur >= pIn_buf_end)
+ {
+ TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS);
+ }
+ n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter);
+ TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n);
+ pIn_buf_cur += n;
+ pOut_buf_cur += n;
+ counter -= (mz_uint)n;
+ }
+ }
+ else if (r->m_type == 3)
+ {
+ TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED);
+ }
+ else
+ {
+ if (r->m_type == 1)
+ {
+ mz_uint8 *p = r->m_tables[0].m_code_size;
+ mz_uint i;
+ r->m_table_sizes[0] = 288;
+ r->m_table_sizes[1] = 32;
+ TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32);
+ for (i = 0; i <= 143; ++i)
+ *p++ = 8;
+ for (; i <= 255; ++i)
+ *p++ = 9;
+ for (; i <= 279; ++i)
+ *p++ = 7;
+ for (; i <= 287; ++i)
+ *p++ = 8;
+ }
+ else
+ {
+ for (counter = 0; counter < 3; counter++)
+ {
+ TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]);
+ r->m_table_sizes[counter] += s_min_table_sizes[counter];
+ }
+ MZ_CLEAR_OBJ(r->m_tables[2].m_code_size);
+ for (counter = 0; counter < r->m_table_sizes[2]; counter++)
+ {
+ mz_uint s;
+ TINFL_GET_BITS(14, s, 3);
+ r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s;
+ }
+ r->m_table_sizes[2] = 19;
+ }
+ for (; (int)r->m_type >= 0; r->m_type--)
+ {
+ int tree_next, tree_cur;
+ tinfl_huff_table *pTable;
+ mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16];
+ pTable = &r->m_tables[r->m_type];
+ MZ_CLEAR_OBJ(total_syms);
+ MZ_CLEAR_OBJ(pTable->m_look_up);
+ MZ_CLEAR_OBJ(pTable->m_tree);
+ for (i = 0; i < r->m_table_sizes[r->m_type]; ++i)
+ total_syms[pTable->m_code_size[i]]++;
+ used_syms = 0, total = 0;
+ next_code[0] = next_code[1] = 0;
+ for (i = 1; i <= 15; ++i)
+ {
+ used_syms += total_syms[i];
+ next_code[i + 1] = (total = ((total + total_syms[i]) << 1));
+ }
+ if ((65536 != total) && (used_syms > 1))
+ {
+ TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED);
+ }
+ for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index)
+ {
+ mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index];
+ if (!code_size)
+ continue;
+ cur_code = next_code[code_size]++;
+ for (l = code_size; l > 0; l--, cur_code >>= 1)
+ rev_code = (rev_code << 1) | (cur_code & 1);
+ if (code_size <= TINFL_FAST_LOOKUP_BITS)
+ {
+ mz_int16 k = (mz_int16)((code_size << 9) | sym_index);
+ while (rev_code < TINFL_FAST_LOOKUP_SIZE)
+ {
+ pTable->m_look_up[rev_code] = k;
+ rev_code += (1 << code_size);
+ }
+ continue;
+ }
+ if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)]))
+ {
+ pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next;
+ tree_cur = tree_next;
+ tree_next -= 2;
+ }
+ rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1);
+ for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--)
+ {
+ tree_cur -= ((rev_code >>= 1) & 1);
+ if (!pTable->m_tree[-tree_cur - 1])
+ {
+ pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next;
+ tree_cur = tree_next;
+ tree_next -= 2;
+ }
+ else
+ tree_cur = pTable->m_tree[-tree_cur - 1];
+ }
+ tree_cur -= ((rev_code >>= 1) & 1);
+ pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index;
+ }
+ if (r->m_type == 2)
+ {
+ for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);)
+ {
+ mz_uint s;
+ TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]);
+ if (dist < 16)
+ {
+ r->m_len_codes[counter++] = (mz_uint8)dist;
+ continue;
+ }
+ if ((dist == 16) && (!counter))
+ {
+ TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED);
+ }
+ num_extra = "\02\03\07"[dist - 16];
+ TINFL_GET_BITS(18, s, num_extra);
+ s += "\03\03\013"[dist - 16];
+ TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s);
+ counter += s;
+ }
+ if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter)
+ {
+ TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED);
+ }
+ TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]);
+ TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]);
+ }
+ }
+ for (;;)
+ {
+ mz_uint8 *pSrc;
+ for (;;)
+ {
+ if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2))
+ {
+ TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]);
+ if (counter >= 256)
+ break;
+ while (pOut_buf_cur >= pOut_buf_end)
+ {
+ TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT);
+ }
+ *pOut_buf_cur++ = (mz_uint8)counter;
+ }
+ else
+ {
+ int sym2;
+ mz_uint code_len;
+#if TINFL_USE_64BIT_BITBUF
+ if (num_bits < 30)
+ {
+ bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits);
+ pIn_buf_cur += 4;
+ num_bits += 32;
+ }
+#else
+ if (num_bits < 15)
+ {
+ bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits);
+ pIn_buf_cur += 2;
+ num_bits += 16;
+ }
+#endif
+ if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
+ code_len = sym2 >> 9;
+ else
+ {
+ code_len = TINFL_FAST_LOOKUP_BITS;
+ do
+ {
+ sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)];
+ } while (sym2 < 0);
+ }
+ counter = sym2;
+ bit_buf >>= code_len;
+ num_bits -= code_len;
+ if (counter & 256)
+ break;
+
+#if !TINFL_USE_64BIT_BITBUF
+ if (num_bits < 15)
+ {
+ bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits);
+ pIn_buf_cur += 2;
+ num_bits += 16;
+ }
+#endif
+ if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
+ code_len = sym2 >> 9;
+ else
+ {
+ code_len = TINFL_FAST_LOOKUP_BITS;
+ do
+ {
+ sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)];
+ } while (sym2 < 0);
+ }
+ bit_buf >>= code_len;
+ num_bits -= code_len;
+
+ pOut_buf_cur[0] = (mz_uint8)counter;
+ if (sym2 & 256)
+ {
+ pOut_buf_cur++;
+ counter = sym2;
+ break;
+ }
+ pOut_buf_cur[1] = (mz_uint8)sym2;
+ pOut_buf_cur += 2;
+ }
+ }
+ if ((counter &= 511) == 256)
+ break;
+
+ num_extra = s_length_extra[counter - 257];
+ counter = s_length_base[counter - 257];
+ if (num_extra)
+ {
+ mz_uint extra_bits;
+ TINFL_GET_BITS(25, extra_bits, num_extra);
+ counter += extra_bits;
+ }
+
+ TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]);
+ num_extra = s_dist_extra[dist];
+ dist = s_dist_base[dist];
+ if (num_extra)
+ {
+ mz_uint extra_bits;
+ TINFL_GET_BITS(27, extra_bits, num_extra);
+ dist += extra_bits;
+ }
+
+ dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start;
+ if ((dist == 0 || dist > dist_from_out_buf_start || dist_from_out_buf_start == 0) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))
+ {
+ TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED);
+ }
+
+ pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask);
+
+ if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end)
+ {
+ while (counter--)
+ {
+ while (pOut_buf_cur >= pOut_buf_end)
+ {
+ TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT);
+ }
+ *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask];
+ }
+ continue;
+ }
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
+ else if ((counter >= 9) && (counter <= dist))
+ {
+ const mz_uint8 *pSrc_end = pSrc + (counter & ~7);
+ do
+ {
+#ifdef MINIZ_UNALIGNED_USE_MEMCPY
+ memcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32)*2);
+#else
+ ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0];
+ ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1];
+#endif
+ pOut_buf_cur += 8;
+ } while ((pSrc += 8) < pSrc_end);
+ if ((counter &= 7) < 3)
+ {
+ if (counter)
+ {
+ pOut_buf_cur[0] = pSrc[0];
+ if (counter > 1)
+ pOut_buf_cur[1] = pSrc[1];
+ pOut_buf_cur += counter;
+ }
+ continue;
+ }
+ }
+#endif
+ while(counter>2)
+ {
+ pOut_buf_cur[0] = pSrc[0];
+ pOut_buf_cur[1] = pSrc[1];
+ pOut_buf_cur[2] = pSrc[2];
+ pOut_buf_cur += 3;
+ pSrc += 3;
+ counter -= 3;
+ }
+ if (counter > 0)
+ {
+ pOut_buf_cur[0] = pSrc[0];
+ if (counter > 1)
+ pOut_buf_cur[1] = pSrc[1];
+ pOut_buf_cur += counter;
+ }
+ }
+ }
+ } while (!(r->m_final & 1));
+
+ /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */
+ /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */
+ TINFL_SKIP_BITS(32, num_bits & 7);
+ while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8))
+ {
+ --pIn_buf_cur;
+ num_bits -= 8;
+ }
+ bit_buf &= (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1);
+ MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */
+
+ if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
+ {
+ for (counter = 0; counter < 4; ++counter)
+ {
+ mz_uint s;
+ if (num_bits)
+ TINFL_GET_BITS(41, s, 8);
+ else
+ TINFL_GET_BYTE(42, s);
+ r->m_z_adler32 = (r->m_z_adler32 << 8) | s;
+ }
+ }
+ TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE);
+
+ TINFL_CR_FINISH
+
+common_exit:
+ /* As long as we aren't telling the caller that we NEED more input to make forward progress: */
+ /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */
+ /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */
+ if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS))
+ {
+ while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8))
+ {
+ --pIn_buf_cur;
+ num_bits -= 8;
+ }
+ }
+ r->m_num_bits = num_bits;
+ r->m_bit_buf = bit_buf & (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1);
+ r->m_dist = dist;
+ r->m_counter = counter;
+ r->m_num_extra = num_extra;
+ r->m_dist_from_out_buf_start = dist_from_out_buf_start;
+ *pIn_buf_size = pIn_buf_cur - pIn_buf_next;
+ *pOut_buf_size = pOut_buf_cur - pOut_buf_next;
+ if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0))
+ {
+ const mz_uint8 *ptr = pOut_buf_next;
+ size_t buf_len = *pOut_buf_size;
+ mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16;
+ size_t block_len = buf_len % 5552;
+ while (buf_len)
+ {
+ for (i = 0; i + 7 < block_len; i += 8, ptr += 8)
+ {
+ s1 += ptr[0], s2 += s1;
+ s1 += ptr[1], s2 += s1;
+ s1 += ptr[2], s2 += s1;
+ s1 += ptr[3], s2 += s1;
+ s1 += ptr[4], s2 += s1;
+ s1 += ptr[5], s2 += s1;
+ s1 += ptr[6], s2 += s1;
+ s1 += ptr[7], s2 += s1;
+ }
+ for (; i < block_len; ++i)
+ s1 += *ptr++, s2 += s1;
+ s1 %= 65521U, s2 %= 65521U;
+ buf_len -= block_len;
+ block_len = 5552;
+ }
+ r->m_check_adler32 = (s2 << 16) + s1;
+ if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32))
+ status = TINFL_STATUS_ADLER32_MISMATCH;
+ }
+ return status;
+}
+
+/* Higher level helper functions. */
+void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)
+{
+ tinfl_decompressor decomp;
+ void *pBuf = NULL, *pNew_buf;
+ size_t src_buf_ofs = 0, out_buf_capacity = 0;
+ *pOut_len = 0;
+ tinfl_init(&decomp);
+ for (;;)
+ {
+ size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity;
+ tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size,
+ (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
+ if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT))
+ {
+ MZ_FREE(pBuf);
+ *pOut_len = 0;
+ return NULL;
+ }
+ src_buf_ofs += src_buf_size;
+ *pOut_len += dst_buf_size;
+ if (status == TINFL_STATUS_DONE)
+ break;
+ new_out_buf_capacity = out_buf_capacity * 2;
+ if (new_out_buf_capacity < 128)
+ new_out_buf_capacity = 128;
+ pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity);
+ if (!pNew_buf)
+ {
+ MZ_FREE(pBuf);
+ *pOut_len = 0;
+ return NULL;
+ }
+ pBuf = pNew_buf;
+ out_buf_capacity = new_out_buf_capacity;
+ }
+ return pBuf;
+}
+
+size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)
+{
+ tinfl_decompressor decomp;
+ tinfl_status status;
+ tinfl_init(&decomp);
+ status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
+ return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len;
+}
+
+int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
+{
+ int result = 0;
+ tinfl_decompressor decomp;
+ mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE);
+ size_t in_buf_ofs = 0, dict_ofs = 0;
+ if (!pDict)
+ return TINFL_STATUS_FAILED;
+ tinfl_init(&decomp);
+ for (;;)
+ {
+ size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs;
+ tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size,
+ (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)));
+ in_buf_ofs += in_buf_size;
+ if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user)))
+ break;
+ if (status != TINFL_STATUS_HAS_MORE_OUTPUT)
+ {
+ result = (status == TINFL_STATUS_DONE);
+ break;
+ }
+ dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1);
+ }
+ MZ_FREE(pDict);
+ *pIn_buf_size = in_buf_ofs;
+ return result;
+}
+
+#ifndef MINIZ_NO_MALLOC
+tinfl_decompressor *tinfl_decompressor_alloc()
+{
+ tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor));
+ if (pDecomp)
+ tinfl_init(pDecomp);
+ return pDecomp;
+}
+
+void tinfl_decompressor_free(tinfl_decompressor *pDecomp)
+{
+ MZ_FREE(pDecomp);
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+ /**************************************************************************
+ *
+ * Copyright 2013-2014 RAD Game Tools and Valve Software
+ * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
+ * Copyright 2016 Martin Raiber
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ **************************************************************************/
+
+
+#ifndef MINIZ_NO_ARCHIVE_APIS
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ------------------- .ZIP archive reading */
+
+#ifdef MINIZ_NO_STDIO
+#define MZ_FILE void *
+#else
+#include
+
+#if defined(_MSC_VER) || defined(__MINGW64__)
+static FILE *mz_fopen(const char *pFilename, const char *pMode)
+{
+ FILE *pFile = NULL;
+ fopen_s(&pFile, pFilename, pMode);
+ return pFile;
+}
+static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream)
+{
+ FILE *pFile = NULL;
+ if (freopen_s(&pFile, pPath, pMode, pStream))
+ return NULL;
+ return pFile;
+}
+#ifndef MINIZ_NO_TIME
+#include
+#endif
+#define MZ_FOPEN mz_fopen
+#define MZ_FCLOSE fclose
+#define MZ_FREAD fread
+#define MZ_FWRITE fwrite
+#define MZ_FTELL64 _ftelli64
+#define MZ_FSEEK64 _fseeki64
+#define MZ_FILE_STAT_STRUCT _stat64
+#define MZ_FILE_STAT _stat64
+#define MZ_FFLUSH fflush
+#define MZ_FREOPEN mz_freopen
+#define MZ_DELETE_FILE remove
+#elif defined(__MINGW32__)
+#ifndef MINIZ_NO_TIME
+#include
+#endif
+#define MZ_FOPEN(f, m) fopen(f, m)
+#define MZ_FCLOSE fclose
+#define MZ_FREAD fread
+#define MZ_FWRITE fwrite
+#define MZ_FTELL64 ftello64
+#define MZ_FSEEK64 fseeko64
+#define MZ_FILE_STAT_STRUCT _stat
+#define MZ_FILE_STAT _stat
+#define MZ_FFLUSH fflush
+#define MZ_FREOPEN(f, m, s) freopen(f, m, s)
+#define MZ_DELETE_FILE remove
+#elif defined(__TINYC__)
+#ifndef MINIZ_NO_TIME
+#include
+#endif
+#define MZ_FOPEN(f, m) fopen(f, m)
+#define MZ_FCLOSE fclose
+#define MZ_FREAD fread
+#define MZ_FWRITE fwrite
+#define MZ_FTELL64 ftell
+#define MZ_FSEEK64 fseek
+#define MZ_FILE_STAT_STRUCT stat
+#define MZ_FILE_STAT stat
+#define MZ_FFLUSH fflush
+#define MZ_FREOPEN(f, m, s) freopen(f, m, s)
+#define MZ_DELETE_FILE remove
+#elif defined(__USE_LARGEFILE64) /* gcc, clang */
+#ifndef MINIZ_NO_TIME
+#include
+#endif
+#define MZ_FOPEN(f, m) fopen64(f, m)
+#define MZ_FCLOSE fclose
+#define MZ_FREAD fread
+#define MZ_FWRITE fwrite
+#define MZ_FTELL64 ftello64
+#define MZ_FSEEK64 fseeko64
+#define MZ_FILE_STAT_STRUCT stat64
+#define MZ_FILE_STAT stat64
+#define MZ_FFLUSH fflush
+#define MZ_FREOPEN(p, m, s) freopen64(p, m, s)
+#define MZ_DELETE_FILE remove
+#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
+#ifndef MINIZ_NO_TIME
+#include
+#endif
+#define MZ_FOPEN(f, m) fopen(f, m)
+#define MZ_FCLOSE fclose
+#define MZ_FREAD fread
+#define MZ_FWRITE fwrite
+#define MZ_FTELL64 ftello
+#define MZ_FSEEK64 fseeko
+#define MZ_FILE_STAT_STRUCT stat
+#define MZ_FILE_STAT stat
+#define MZ_FFLUSH fflush
+#define MZ_FREOPEN(p, m, s) freopen(p, m, s)
+#define MZ_DELETE_FILE remove
+
+#else
+#if !defined(__MSYS__) && !defined(__CYGWIN__)
+#pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.")
+#endif
+#ifndef MINIZ_NO_TIME
+#include
+#endif
+#define MZ_FOPEN(f, m) fopen(f, m)
+#define MZ_FCLOSE fclose
+#define MZ_FREAD fread
+#define MZ_FWRITE fwrite
+#ifdef __STRICT_ANSI__
+#define MZ_FTELL64 ftell
+#define MZ_FSEEK64 fseek
+#else
+#define MZ_FTELL64 ftello
+#define MZ_FSEEK64 fseeko
+#endif
+#define MZ_FILE_STAT_STRUCT stat
+#define MZ_FILE_STAT stat
+#define MZ_FFLUSH fflush
+#define MZ_FREOPEN(f, m, s) freopen(f, m, s)
+#define MZ_DELETE_FILE remove
+#endif /* #ifdef _MSC_VER */
+#endif /* #ifdef MINIZ_NO_STDIO */
+
+#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
+
+/* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */
+enum
+{
+ /* ZIP archive identifiers and record sizes */
+ MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50,
+ MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50,
+ MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50,
+ MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30,
+ MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46,
+ MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22,
+
+ /* ZIP64 archive identifier and record sizes */
+ MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50,
+ MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50,
+ MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56,
+ MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20,
+ MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001,
+ MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50,
+ MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24,
+ MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16,
+
+ /* Central directory header record offsets */
+ MZ_ZIP_CDH_SIG_OFS = 0,
+ MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4,
+ MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6,
+ MZ_ZIP_CDH_BIT_FLAG_OFS = 8,
+ MZ_ZIP_CDH_METHOD_OFS = 10,
+ MZ_ZIP_CDH_FILE_TIME_OFS = 12,
+ MZ_ZIP_CDH_FILE_DATE_OFS = 14,
+ MZ_ZIP_CDH_CRC32_OFS = 16,
+ MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20,
+ MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24,
+ MZ_ZIP_CDH_FILENAME_LEN_OFS = 28,
+ MZ_ZIP_CDH_EXTRA_LEN_OFS = 30,
+ MZ_ZIP_CDH_COMMENT_LEN_OFS = 32,
+ MZ_ZIP_CDH_DISK_START_OFS = 34,
+ MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36,
+ MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38,
+ MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42,
+
+ /* Local directory header offsets */
+ MZ_ZIP_LDH_SIG_OFS = 0,
+ MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4,
+ MZ_ZIP_LDH_BIT_FLAG_OFS = 6,
+ MZ_ZIP_LDH_METHOD_OFS = 8,
+ MZ_ZIP_LDH_FILE_TIME_OFS = 10,
+ MZ_ZIP_LDH_FILE_DATE_OFS = 12,
+ MZ_ZIP_LDH_CRC32_OFS = 14,
+ MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18,
+ MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22,
+ MZ_ZIP_LDH_FILENAME_LEN_OFS = 26,
+ MZ_ZIP_LDH_EXTRA_LEN_OFS = 28,
+ MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3,
+
+ /* End of central directory offsets */
+ MZ_ZIP_ECDH_SIG_OFS = 0,
+ MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4,
+ MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6,
+ MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8,
+ MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10,
+ MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12,
+ MZ_ZIP_ECDH_CDIR_OFS_OFS = 16,
+ MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20,
+
+ /* ZIP64 End of central directory locator offsets */
+ MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */
+ MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */
+ MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */
+ MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */
+
+ /* ZIP64 End of central directory header offsets */
+ MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */
+ MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */
+ MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */
+ MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */
+ MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */
+ MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */
+ MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */
+ MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */
+ MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */
+ MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */
+ MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0,
+ MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10,
+ MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1,
+ MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32,
+ MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64,
+ MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192,
+ MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11
+};
+
+typedef struct
+{
+ void *m_p;
+ size_t m_size, m_capacity;
+ mz_uint m_element_size;
+} mz_zip_array;
+
+struct mz_zip_internal_state_tag
+{
+ mz_zip_array m_central_dir;
+ mz_zip_array m_central_dir_offsets;
+ mz_zip_array m_sorted_central_dir_offsets;
+
+ /* The flags passed in when the archive is initially opened. */
+ uint32_t m_init_flags;
+
+ /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */
+ mz_bool m_zip64;
+
+ /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */
+ mz_bool m_zip64_has_extended_info_fields;
+
+ /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */
+ MZ_FILE *m_pFile;
+ mz_uint64 m_file_archive_start_ofs;
+
+ void *m_pMem;
+ size_t m_mem_size;
+ size_t m_mem_capacity;
+};
+
+#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size
+
+#if defined(DEBUG) || defined(_DEBUG)
+static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index)
+{
+ MZ_ASSERT(index < pArray->m_size);
+ return index;
+}
+#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)]
+#else
+#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index]
+#endif
+
+static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size)
+{
+ memset(pArray, 0, sizeof(mz_zip_array));
+ pArray->m_element_size = element_size;
+}
+
+static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray)
+{
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p);
+ memset(pArray, 0, sizeof(mz_zip_array));
+}
+
+static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing)
+{
+ void *pNew_p;
+ size_t new_capacity = min_new_capacity;
+ MZ_ASSERT(pArray->m_element_size);
+ if (pArray->m_capacity >= min_new_capacity)
+ return MZ_TRUE;
+ if (growing)
+ {
+ new_capacity = MZ_MAX(1, pArray->m_capacity);
+ while (new_capacity < min_new_capacity)
+ new_capacity *= 2;
+ }
+ if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity)))
+ return MZ_FALSE;
+ pArray->m_p = pNew_p;
+ pArray->m_capacity = new_capacity;
+ return MZ_TRUE;
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing)
+{
+ if (new_capacity > pArray->m_capacity)
+ {
+ if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing))
+ return MZ_FALSE;
+ }
+ return MZ_TRUE;
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing)
+{
+ if (new_size > pArray->m_capacity)
+ {
+ if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing))
+ return MZ_FALSE;
+ }
+ pArray->m_size = new_size;
+ return MZ_TRUE;
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n)
+{
+ return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE);
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n)
+{
+ size_t orig_size = pArray->m_size;
+ if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE))
+ return MZ_FALSE;
+ if (n > 0)
+ memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size);
+ return MZ_TRUE;
+}
+
+#ifndef MINIZ_NO_TIME
+static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date)
+{
+ struct tm tm;
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_isdst = -1;
+ tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900;
+ tm.tm_mon = ((dos_date >> 5) & 15) - 1;
+ tm.tm_mday = dos_date & 31;
+ tm.tm_hour = (dos_time >> 11) & 31;
+ tm.tm_min = (dos_time >> 5) & 63;
+ tm.tm_sec = (dos_time << 1) & 62;
+ return mktime(&tm);
+}
+
+#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
+static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date)
+{
+#ifdef _MSC_VER
+ struct tm tm_struct;
+ struct tm *tm = &tm_struct;
+ errno_t err = localtime_s(tm, &time);
+ if (err)
+ {
+ *pDOS_date = 0;
+ *pDOS_time = 0;
+ return;
+ }
+#else
+ struct tm *tm = localtime(&time);
+#endif /* #ifdef _MSC_VER */
+
+ *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1));
+ *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday);
+}
+#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */
+
+#ifndef MINIZ_NO_STDIO
+#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
+static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime)
+{
+ struct MZ_FILE_STAT_STRUCT file_stat;
+
+ /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */
+ if (MZ_FILE_STAT(pFilename, &file_stat) != 0)
+ return MZ_FALSE;
+
+ *pTime = file_stat.st_mtime;
+
+ return MZ_TRUE;
+}
+#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/
+
+static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time)
+{
+ struct utimbuf t;
+
+ memset(&t, 0, sizeof(t));
+ t.actime = access_time;
+ t.modtime = modified_time;
+
+ return !utime(pFilename, &t);
+}
+#endif /* #ifndef MINIZ_NO_STDIO */
+#endif /* #ifndef MINIZ_NO_TIME */
+
+static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num)
+{
+ if (pZip)
+ pZip->m_last_error = err_num;
+ return MZ_FALSE;
+}
+
+static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags)
+{
+ (void)flags;
+ if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (!pZip->m_pAlloc)
+ pZip->m_pAlloc = miniz_def_alloc_func;
+ if (!pZip->m_pFree)
+ pZip->m_pFree = miniz_def_free_func;
+ if (!pZip->m_pRealloc)
+ pZip->m_pRealloc = miniz_def_realloc_func;
+
+ pZip->m_archive_size = 0;
+ pZip->m_central_directory_file_ofs = 0;
+ pZip->m_total_files = 0;
+ pZip->m_last_error = MZ_ZIP_NO_ERROR;
+
+ if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+
+ memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));
+ MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));
+ MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));
+ MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));
+ pZip->m_pState->m_init_flags = flags;
+ pZip->m_pState->m_zip64 = MZ_FALSE;
+ pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE;
+
+ pZip->m_zip_mode = MZ_ZIP_MODE_READING;
+
+ return MZ_TRUE;
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index)
+{
+ const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;
+ const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index));
+ mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+ mz_uint8 l = 0, r = 0;
+ pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
+ pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
+ pE = pL + MZ_MIN(l_len, r_len);
+ while (pL < pE)
+ {
+ if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))
+ break;
+ pL++;
+ pR++;
+ }
+ return (pL == pE) ? (l_len < r_len) : (l < r);
+}
+
+#define MZ_SWAP_UINT32(a, b) \
+ do \
+ { \
+ mz_uint32 t = a; \
+ a = b; \
+ b = t; \
+ } \
+ MZ_MACRO_END
+
+/* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */
+static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip)
+{
+ mz_zip_internal_state *pState = pZip->m_pState;
+ const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
+ const mz_zip_array *pCentral_dir = &pState->m_central_dir;
+ mz_uint32 *pIndices;
+ mz_uint32 start, end;
+ const mz_uint32 size = pZip->m_total_files;
+
+ if (size <= 1U)
+ return;
+
+ pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);
+
+ start = (size - 2U) >> 1U;
+ for (;;)
+ {
+ mz_uint64 child, root = start;
+ for (;;)
+ {
+ if ((child = (root << 1U) + 1U) >= size)
+ break;
+ child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])));
+ if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
+ break;
+ MZ_SWAP_UINT32(pIndices[root], pIndices[child]);
+ root = child;
+ }
+ if (!start)
+ break;
+ start--;
+ }
+
+ end = size - 1;
+ while (end > 0)
+ {
+ mz_uint64 child, root = 0;
+ MZ_SWAP_UINT32(pIndices[end], pIndices[0]);
+ for (;;)
+ {
+ if ((child = (root << 1U) + 1U) >= end)
+ break;
+ child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]));
+ if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
+ break;
+ MZ_SWAP_UINT32(pIndices[root], pIndices[child]);
+ root = child;
+ }
+ end--;
+ }
+}
+
+static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs)
+{
+ mz_int64 cur_file_ofs;
+ mz_uint32 buf_u32[4096 / sizeof(mz_uint32)];
+ mz_uint8 *pBuf = (mz_uint8 *)buf_u32;
+
+ /* Basic sanity checks - reject files which are too small */
+ if (pZip->m_archive_size < record_size)
+ return MZ_FALSE;
+
+ /* Find the record by scanning the file from the end towards the beginning. */
+ cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0);
+ for (;;)
+ {
+ int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs);
+
+ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n)
+ return MZ_FALSE;
+
+ for (i = n - 4; i >= 0; --i)
+ {
+ mz_uint s = MZ_READ_LE32(pBuf + i);
+ if (s == record_sig)
+ {
+ if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size)
+ break;
+ }
+ }
+
+ if (i >= 0)
+ {
+ cur_file_ofs += i;
+ break;
+ }
+
+ /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */
+ if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size)))
+ return MZ_FALSE;
+
+ cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0);
+ }
+
+ *pOfs = cur_file_ofs;
+ return MZ_TRUE;
+}
+
+static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags)
+{
+ mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0;
+ mz_uint64 cdir_ofs = 0;
+ mz_int64 cur_file_ofs = 0;
+ const mz_uint8 *p;
+
+ mz_uint32 buf_u32[4096 / sizeof(mz_uint32)];
+ mz_uint8 *pBuf = (mz_uint8 *)buf_u32;
+ mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0);
+ mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
+ mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32;
+
+ mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
+ mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32;
+
+ mz_uint64 zip64_end_of_central_dir_ofs = 0;
+
+ /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */
+ if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
+
+ if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs))
+ return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR);
+
+ /* Read and verify the end of central directory record. */
+ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+
+ if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG)
+ return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
+
+ if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE))
+ {
+ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE)
+ {
+ if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG)
+ {
+ zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS);
+ if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE))
+ return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
+
+ if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)
+ {
+ if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG)
+ {
+ pZip->m_pState->m_zip64 = MZ_TRUE;
+ }
+ }
+ }
+ }
+ }
+
+ pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS);
+ cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS);
+ num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS);
+ cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS);
+ cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS);
+ cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS);
+
+ if (pZip->m_pState->m_zip64)
+ {
+ mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS);
+ mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS);
+ mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS);
+ mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS);
+ mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS);
+
+ if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ if (zip64_total_num_of_disks != 1U)
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);
+
+ /* Check for miniz's practical limits */
+ if (zip64_cdir_total_entries > MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+
+ pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries;
+
+ if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+
+ cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk;
+
+ /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */
+ if (zip64_size_of_central_directory > MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);
+
+ cdir_size = (mz_uint32)zip64_size_of_central_directory;
+
+ num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS);
+
+ cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS);
+
+ cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS);
+ }
+
+ if (pZip->m_total_files != cdir_entries_on_this_disk)
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);
+
+ if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1)))
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);
+
+ if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ pZip->m_central_directory_file_ofs = cdir_ofs;
+
+ if (pZip->m_total_files)
+ {
+ mz_uint i, n;
+ /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */
+ if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) ||
+ (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE)))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+
+ if (sort_central_dir)
+ {
+ if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+
+ /* Now create an index into the central directory file records, do some basic sanity checking on each record */
+ p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p;
+ for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i)
+ {
+ mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size;
+ mz_uint64 comp_size, decomp_size, local_header_ofs;
+
+ if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p);
+
+ if (sort_central_dir)
+ MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i;
+
+ comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
+ decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
+ local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS);
+ filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+ ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS);
+
+ if ((!pZip->m_pState->m_zip64_has_extended_info_fields) &&
+ (ext_data_size) &&
+ (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX))
+ {
+ /* Attempt to find zip64 extended information field in the entry's extra data */
+ mz_uint32 extra_size_remaining = ext_data_size;
+
+ if (extra_size_remaining)
+ {
+ const mz_uint8 *pExtra_data;
+ void* buf = NULL;
+
+ if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n)
+ {
+ buf = MZ_MALLOC(ext_data_size);
+ if(buf==NULL)
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+
+ if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size)
+ {
+ MZ_FREE(buf);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ }
+
+ pExtra_data = (mz_uint8*)buf;
+ }
+ else
+ {
+ pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size;
+ }
+
+ do
+ {
+ mz_uint32 field_id;
+ mz_uint32 field_data_size;
+
+ if (extra_size_remaining < (sizeof(mz_uint16) * 2))
+ {
+ MZ_FREE(buf);
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ }
+
+ field_id = MZ_READ_LE16(pExtra_data);
+ field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
+
+ if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining)
+ {
+ MZ_FREE(buf);
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ }
+
+ if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
+ {
+ /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */
+ pZip->m_pState->m_zip64 = MZ_TRUE;
+ pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE;
+ break;
+ }
+
+ pExtra_data += sizeof(mz_uint16) * 2 + field_data_size;
+ extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size;
+ } while (extra_size_remaining);
+
+ MZ_FREE(buf);
+ }
+ }
+
+ /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */
+ if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX))
+ {
+ if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ }
+
+ disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS);
+ if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1)))
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);
+
+ if (comp_size != MZ_UINT32_MAX)
+ {
+ if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ }
+
+ bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
+ if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED)
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);
+
+ if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ n -= total_header_size;
+ p += total_header_size;
+ }
+ }
+
+ if (sort_central_dir)
+ mz_zip_reader_sort_central_dir_offsets_by_filename(pZip);
+
+ return MZ_TRUE;
+}
+
+void mz_zip_zero_struct(mz_zip_archive *pZip)
+{
+ if (pZip)
+ MZ_CLEAR_OBJ(*pZip);
+}
+
+static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error)
+{
+ mz_bool status = MZ_TRUE;
+
+ if (!pZip)
+ return MZ_FALSE;
+
+ if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
+ {
+ if (set_last_error)
+ pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER;
+
+ return MZ_FALSE;
+ }
+
+ if (pZip->m_pState)
+ {
+ mz_zip_internal_state *pState = pZip->m_pState;
+ pZip->m_pState = NULL;
+
+ mz_zip_array_clear(pZip, &pState->m_central_dir);
+ mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);
+ mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);
+
+#ifndef MINIZ_NO_STDIO
+ if (pState->m_pFile)
+ {
+ if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)
+ {
+ if (MZ_FCLOSE(pState->m_pFile) == EOF)
+ {
+ if (set_last_error)
+ pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED;
+ status = MZ_FALSE;
+ }
+ }
+ pState->m_pFile = NULL;
+ }
+#endif /* #ifndef MINIZ_NO_STDIO */
+
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+ }
+ pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;
+
+ return status;
+}
+
+mz_bool mz_zip_reader_end(mz_zip_archive *pZip)
+{
+ return mz_zip_reader_end_internal(pZip, MZ_TRUE);
+}
+mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags)
+{
+ if ((!pZip) || (!pZip->m_pRead))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (!mz_zip_reader_init_internal(pZip, flags))
+ return MZ_FALSE;
+
+ pZip->m_zip_type = MZ_ZIP_TYPE_USER;
+ pZip->m_archive_size = size;
+
+ if (!mz_zip_reader_read_central_dir(pZip, flags))
+ {
+ mz_zip_reader_end_internal(pZip, MZ_FALSE);
+ return MZ_FALSE;
+ }
+
+ return MZ_TRUE;
+}
+
+static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
+{
+ mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
+ size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n);
+ memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s);
+ return s;
+}
+
+mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags)
+{
+ if (!pMem)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
+
+ if (!mz_zip_reader_init_internal(pZip, flags))
+ return MZ_FALSE;
+
+ pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY;
+ pZip->m_archive_size = size;
+ pZip->m_pRead = mz_zip_mem_read_func;
+ pZip->m_pIO_opaque = pZip;
+ pZip->m_pNeeds_keepalive = NULL;
+
+#ifdef __cplusplus
+ pZip->m_pState->m_pMem = const_cast(pMem);
+#else
+ pZip->m_pState->m_pMem = (void *)pMem;
+#endif
+
+ pZip->m_pState->m_mem_size = size;
+
+ if (!mz_zip_reader_read_central_dir(pZip, flags))
+ {
+ mz_zip_reader_end_internal(pZip, MZ_FALSE);
+ return MZ_FALSE;
+ }
+
+ return MZ_TRUE;
+}
+
+#ifndef MINIZ_NO_STDIO
+static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
+{
+ mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
+ mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);
+
+ file_ofs += pZip->m_pState->m_file_archive_start_ofs;
+
+ if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))
+ return 0;
+
+ return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile);
+}
+
+mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags)
+{
+ return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0);
+}
+
+mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size)
+{
+ mz_uint64 file_size;
+ MZ_FILE *pFile;
+
+ if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ pFile = MZ_FOPEN(pFilename, "rb");
+ if (!pFile)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);
+
+ file_size = archive_size;
+ if (!file_size)
+ {
+ if (MZ_FSEEK64(pFile, 0, SEEK_END))
+ {
+ MZ_FCLOSE(pFile);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);
+ }
+
+ file_size = MZ_FTELL64(pFile);
+ }
+
+ /* TODO: Better sanity check archive_size and the # of actual remaining bytes */
+
+ if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
+ {
+ MZ_FCLOSE(pFile);
+ return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
+ }
+
+ if (!mz_zip_reader_init_internal(pZip, flags))
+ {
+ MZ_FCLOSE(pFile);
+ return MZ_FALSE;
+ }
+
+ pZip->m_zip_type = MZ_ZIP_TYPE_FILE;
+ pZip->m_pRead = mz_zip_file_read_func;
+ pZip->m_pIO_opaque = pZip;
+ pZip->m_pState->m_pFile = pFile;
+ pZip->m_archive_size = file_size;
+ pZip->m_pState->m_file_archive_start_ofs = file_start_ofs;
+
+ if (!mz_zip_reader_read_central_dir(pZip, flags))
+ {
+ mz_zip_reader_end_internal(pZip, MZ_FALSE);
+ return MZ_FALSE;
+ }
+
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags)
+{
+ mz_uint64 cur_file_ofs;
+
+ if ((!pZip) || (!pFile))
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);
+
+ cur_file_ofs = MZ_FTELL64(pFile);
+
+ if (!archive_size)
+ {
+ if (MZ_FSEEK64(pFile, 0, SEEK_END))
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);
+
+ archive_size = MZ_FTELL64(pFile) - cur_file_ofs;
+
+ if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
+ }
+
+ if (!mz_zip_reader_init_internal(pZip, flags))
+ return MZ_FALSE;
+
+ pZip->m_zip_type = MZ_ZIP_TYPE_CFILE;
+ pZip->m_pRead = mz_zip_file_read_func;
+
+ pZip->m_pIO_opaque = pZip;
+ pZip->m_pState->m_pFile = pFile;
+ pZip->m_archive_size = archive_size;
+ pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs;
+
+ if (!mz_zip_reader_read_central_dir(pZip, flags))
+ {
+ mz_zip_reader_end_internal(pZip, MZ_FALSE);
+ return MZ_FALSE;
+ }
+
+ return MZ_TRUE;
+}
+
+#endif /* #ifndef MINIZ_NO_STDIO */
+
+static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index)
+{
+ if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files))
+ return NULL;
+ return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));
+}
+
+mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index)
+{
+ mz_uint m_bit_flag;
+ const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
+ if (!p)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+ return MZ_FALSE;
+ }
+
+ m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
+ return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0;
+}
+
+mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index)
+{
+ mz_uint bit_flag;
+ mz_uint method;
+
+ const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
+ if (!p)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+ return MZ_FALSE;
+ }
+
+ method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS);
+ bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
+
+ if ((method != 0) && (method != MZ_DEFLATED))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);
+ return MZ_FALSE;
+ }
+
+ if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);
+ return MZ_FALSE;
+ }
+
+ if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);
+ return MZ_FALSE;
+ }
+
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index)
+{
+ mz_uint filename_len, attribute_mapping_id, external_attr;
+ const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
+ if (!p)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+ return MZ_FALSE;
+ }
+
+ filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+ if (filename_len)
+ {
+ if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/')
+ return MZ_TRUE;
+ }
+
+ /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */
+ /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */
+ /* FIXME: Remove this check? Is it necessary - we already check the filename. */
+ attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8;
+ (void)attribute_mapping_id;
+
+ external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
+ if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0)
+ {
+ return MZ_TRUE;
+ }
+
+ return MZ_FALSE;
+}
+
+static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data)
+{
+ mz_uint n;
+ const mz_uint8 *p = pCentral_dir_header;
+
+ if (pFound_zip64_extra_data)
+ *pFound_zip64_extra_data = MZ_FALSE;
+
+ if ((!p) || (!pStat))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ /* Extract fields from the central directory record. */
+ pStat->m_file_index = file_index;
+ pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index);
+ pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS);
+ pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS);
+ pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
+ pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS);
+#ifndef MINIZ_NO_TIME
+ pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS));
+#endif
+ pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS);
+ pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
+ pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
+ pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS);
+ pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
+ pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS);
+
+ /* Copy as much of the filename and comment as possible. */
+ n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+ n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1);
+ memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n);
+ pStat->m_filename[n] = '\0';
+
+ n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS);
+ n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1);
+ pStat->m_comment_size = n;
+ memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n);
+ pStat->m_comment[n] = '\0';
+
+ /* Set some flags for convienance */
+ pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index);
+ pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index);
+ pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index);
+
+ /* See if we need to read any zip64 extended information fields. */
+ /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */
+ if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX)
+ {
+ /* Attempt to find zip64 extended information field in the entry's extra data */
+ mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS);
+
+ if (extra_size_remaining)
+ {
+ const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+
+ do
+ {
+ mz_uint32 field_id;
+ mz_uint32 field_data_size;
+
+ if (extra_size_remaining < (sizeof(mz_uint16) * 2))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ field_id = MZ_READ_LE16(pExtra_data);
+ field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
+
+ if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
+ {
+ const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2;
+ mz_uint32 field_data_remaining = field_data_size;
+
+ if (pFound_zip64_extra_data)
+ *pFound_zip64_extra_data = MZ_TRUE;
+
+ if (pStat->m_uncomp_size == MZ_UINT32_MAX)
+ {
+ if (field_data_remaining < sizeof(mz_uint64))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ pStat->m_uncomp_size = MZ_READ_LE64(pField_data);
+ pField_data += sizeof(mz_uint64);
+ field_data_remaining -= sizeof(mz_uint64);
+ }
+
+ if (pStat->m_comp_size == MZ_UINT32_MAX)
+ {
+ if (field_data_remaining < sizeof(mz_uint64))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ pStat->m_comp_size = MZ_READ_LE64(pField_data);
+ pField_data += sizeof(mz_uint64);
+ field_data_remaining -= sizeof(mz_uint64);
+ }
+
+ if (pStat->m_local_header_ofs == MZ_UINT32_MAX)
+ {
+ if (field_data_remaining < sizeof(mz_uint64))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ pStat->m_local_header_ofs = MZ_READ_LE64(pField_data);
+ pField_data += sizeof(mz_uint64);
+ field_data_remaining -= sizeof(mz_uint64);
+ }
+
+ break;
+ }
+
+ pExtra_data += sizeof(mz_uint16) * 2 + field_data_size;
+ extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size;
+ } while (extra_size_remaining);
+ }
+ }
+
+ return MZ_TRUE;
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags)
+{
+ mz_uint i;
+ if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE)
+ return 0 == memcmp(pA, pB, len);
+ for (i = 0; i < len; ++i)
+ if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i]))
+ return MZ_FALSE;
+ return MZ_TRUE;
+}
+
+static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len)
+{
+ const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;
+ mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+ mz_uint8 l = 0, r = 0;
+ pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
+ pE = pL + MZ_MIN(l_len, r_len);
+ while (pL < pE)
+ {
+ if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))
+ break;
+ pL++;
+ pR++;
+ }
+ return (pL == pE) ? (int)(l_len - r_len) : (l - r);
+}
+
+static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex)
+{
+ mz_zip_internal_state *pState = pZip->m_pState;
+ const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
+ const mz_zip_array *pCentral_dir = &pState->m_central_dir;
+ mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);
+ const uint32_t size = pZip->m_total_files;
+ const mz_uint filename_len = (mz_uint)strlen(pFilename);
+
+ if (pIndex)
+ *pIndex = 0;
+
+ if (size)
+ {
+ /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */
+ /* honestly the major expense here on 32-bit CPU's will still be the filename compare */
+ mz_int64 l = 0, h = (mz_int64)size - 1;
+
+ while (l <= h)
+ {
+ mz_int64 m = l + ((h - l) >> 1);
+ uint32_t file_index = pIndices[(uint32_t)m];
+
+ int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len);
+ if (!comp)
+ {
+ if (pIndex)
+ *pIndex = file_index;
+ return MZ_TRUE;
+ }
+ else if (comp < 0)
+ l = m + 1;
+ else
+ h = m - 1;
+ }
+ }
+
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND);
+}
+
+int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags)
+{
+ mz_uint32 index;
+ if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index))
+ return -1;
+ else
+ return (int)index;
+}
+
+mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex)
+{
+ mz_uint file_index;
+ size_t name_len, comment_len;
+
+ if (pIndex)
+ *pIndex = 0;
+
+ if ((!pZip) || (!pZip->m_pState) || (!pName))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ /* See if we can use a binary search */
+ if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) &&
+ (pZip->m_zip_mode == MZ_ZIP_MODE_READING) &&
+ ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size))
+ {
+ return mz_zip_locate_file_binary_search(pZip, pName, pIndex);
+ }
+
+ /* Locate the entry by scanning the entire central directory */
+ name_len = strlen(pName);
+ if (name_len > MZ_UINT16_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ comment_len = pComment ? strlen(pComment) : 0;
+ if (comment_len > MZ_UINT16_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ for (file_index = 0; file_index < pZip->m_total_files; file_index++)
+ {
+ const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));
+ mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+ const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
+ if (filename_len < name_len)
+ continue;
+ if (comment_len)
+ {
+ mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS);
+ const char *pFile_comment = pFilename + filename_len + file_extra_len;
+ if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags)))
+ continue;
+ }
+ if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len))
+ {
+ int ofs = filename_len - 1;
+ do
+ {
+ if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':'))
+ break;
+ } while (--ofs >= 0);
+ ofs++;
+ pFilename += ofs;
+ filename_len -= ofs;
+ }
+ if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags)))
+ {
+ if (pIndex)
+ *pIndex = file_index;
+ return MZ_TRUE;
+ }
+ }
+
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND);
+}
+
+mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
+{
+ int status = TINFL_STATUS_DONE;
+ mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail;
+ mz_zip_archive_file_stat file_stat;
+ void *pRead_buf;
+ mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
+ mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
+ tinfl_decompressor inflator;
+
+ if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
+ return MZ_FALSE;
+
+ /* A directory or zero length file */
+ if ((file_stat.m_is_directory) || (!file_stat.m_comp_size))
+ return MZ_TRUE;
+
+ /* Encryption and patch files are not supported. */
+ if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);
+
+ /* This function only supports decompressing stored and deflate. */
+ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);
+
+ /* Ensure supplied output buffer is large enough. */
+ needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size;
+ if (buf_size < needed_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL);
+
+ /* Read and parse the local directory entry. */
+ cur_file_ofs = file_stat.m_local_header_ofs;
+ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+
+ if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
+ if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))
+ {
+ /* The file is stored or the caller has requested the compressed data. */
+ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0)
+ {
+ if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)
+ return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED);
+ }
+#endif
+
+ return MZ_TRUE;
+ }
+
+ /* Decompress the file either directly from memory or from a file input buffer. */
+ tinfl_init(&inflator);
+
+ if (pZip->m_pState->m_pMem)
+ {
+ /* Read directly from the archive in memory. */
+ pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;
+ read_buf_size = read_buf_avail = file_stat.m_comp_size;
+ comp_remaining = 0;
+ }
+ else if (pUser_read_buf)
+ {
+ /* Use a user provided read buffer. */
+ if (!user_read_buf_size)
+ return MZ_FALSE;
+ pRead_buf = (mz_uint8 *)pUser_read_buf;
+ read_buf_size = user_read_buf_size;
+ read_buf_avail = 0;
+ comp_remaining = file_stat.m_comp_size;
+ }
+ else
+ {
+ /* Temporarily allocate a read buffer. */
+ read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);
+ if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF))
+ return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
+
+ if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+
+ read_buf_avail = 0;
+ comp_remaining = file_stat.m_comp_size;
+ }
+
+ do
+ {
+ /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */
+ size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs);
+ if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))
+ {
+ read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
+ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
+ {
+ status = TINFL_STATUS_FAILED;
+ mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);
+ break;
+ }
+ cur_file_ofs += read_buf_avail;
+ comp_remaining -= read_buf_avail;
+ read_buf_ofs = 0;
+ }
+ in_buf_size = (size_t)read_buf_avail;
+ status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0));
+ read_buf_avail -= in_buf_size;
+ read_buf_ofs += in_buf_size;
+ out_buf_ofs += out_buf_size;
+ } while (status == TINFL_STATUS_NEEDS_MORE_INPUT);
+
+ if (status == TINFL_STATUS_DONE)
+ {
+ /* Make sure the entire file was decompressed, and check its CRC. */
+ if (out_buf_ofs != file_stat.m_uncomp_size)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);
+ status = TINFL_STATUS_FAILED;
+ }
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED);
+ status = TINFL_STATUS_FAILED;
+ }
+#endif
+ }
+
+ if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf))
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+
+ return status == TINFL_STATUS_DONE;
+}
+
+mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
+{
+ mz_uint32 file_index;
+ if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
+ return MZ_FALSE;
+ return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size);
+}
+
+mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags)
+{
+ return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0);
+}
+
+mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags)
+{
+ return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0);
+}
+
+void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags)
+{
+ mz_uint64 comp_size, uncomp_size, alloc_size;
+ const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
+ void *pBuf;
+
+ if (pSize)
+ *pSize = 0;
+
+ if (!p)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+ return NULL;
+ }
+
+ comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
+ uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
+
+ alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size;
+ if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
+ return NULL;
+ }
+
+ if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size)))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ return NULL;
+ }
+
+ if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags))
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+ return NULL;
+ }
+
+ if (pSize)
+ *pSize = (size_t)alloc_size;
+ return pBuf;
+}
+
+void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags)
+{
+ mz_uint32 file_index;
+ if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
+ {
+ if (pSize)
+ *pSize = 0;
+ return MZ_FALSE;
+ }
+ return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags);
+}
+
+mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)
+{
+ int status = TINFL_STATUS_DONE;
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ mz_uint file_crc32 = MZ_CRC32_INIT;
+#endif
+ mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs;
+ mz_zip_archive_file_stat file_stat;
+ void *pRead_buf = NULL;
+ void *pWrite_buf = NULL;
+ mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
+ mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
+
+ if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
+ return MZ_FALSE;
+
+ /* A directory or zero length file */
+ if ((file_stat.m_is_directory) || (!file_stat.m_comp_size))
+ return MZ_TRUE;
+
+ /* Encryption and patch files are not supported. */
+ if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);
+
+ /* This function only supports decompressing stored and deflate. */
+ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);
+
+ /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */
+ cur_file_ofs = file_stat.m_local_header_ofs;
+ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+
+ if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
+ if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ /* Decompress the file either directly from memory or from a file input buffer. */
+ if (pZip->m_pState->m_pMem)
+ {
+ pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;
+ read_buf_size = read_buf_avail = file_stat.m_comp_size;
+ comp_remaining = 0;
+ }
+ else
+ {
+ read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);
+ if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+
+ read_buf_avail = 0;
+ comp_remaining = file_stat.m_comp_size;
+ }
+
+ if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))
+ {
+ /* The file is stored or the caller has requested the compressed data. */
+ if (pZip->m_pState->m_pMem)
+ {
+ if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX))
+ return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
+
+ if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);
+ status = TINFL_STATUS_FAILED;
+ }
+ else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
+ {
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size);
+#endif
+ }
+
+ cur_file_ofs += file_stat.m_comp_size;
+ out_buf_ofs += file_stat.m_comp_size;
+ comp_remaining = 0;
+ }
+ else
+ {
+ while (comp_remaining)
+ {
+ read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
+ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ status = TINFL_STATUS_FAILED;
+ break;
+ }
+
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
+ {
+ file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail);
+ }
+#endif
+
+ if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);
+ status = TINFL_STATUS_FAILED;
+ break;
+ }
+
+ cur_file_ofs += read_buf_avail;
+ out_buf_ofs += read_buf_avail;
+ comp_remaining -= read_buf_avail;
+ }
+ }
+ }
+ else
+ {
+ tinfl_decompressor inflator;
+ tinfl_init(&inflator);
+
+ if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE)))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ status = TINFL_STATUS_FAILED;
+ }
+ else
+ {
+ do
+ {
+ mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
+ size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
+ if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))
+ {
+ read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
+ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ status = TINFL_STATUS_FAILED;
+ break;
+ }
+ cur_file_ofs += read_buf_avail;
+ comp_remaining -= read_buf_avail;
+ read_buf_ofs = 0;
+ }
+
+ in_buf_size = (size_t)read_buf_avail;
+ status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0);
+ read_buf_avail -= in_buf_size;
+ read_buf_ofs += in_buf_size;
+
+ if (out_buf_size)
+ {
+ if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);
+ status = TINFL_STATUS_FAILED;
+ break;
+ }
+
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size);
+#endif
+ if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);
+ status = TINFL_STATUS_FAILED;
+ break;
+ }
+ }
+ } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT));
+ }
+ }
+
+ if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
+ {
+ /* Make sure the entire file was decompressed, and check its CRC. */
+ if (out_buf_ofs != file_stat.m_uncomp_size)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);
+ status = TINFL_STATUS_FAILED;
+ }
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ else if (file_crc32 != file_stat.m_crc32)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);
+ status = TINFL_STATUS_FAILED;
+ }
+#endif
+ }
+
+ if (!pZip->m_pState->m_pMem)
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+
+ if (pWrite_buf)
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf);
+
+ return status == TINFL_STATUS_DONE;
+}
+
+mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)
+{
+ mz_uint32 file_index;
+ if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
+ return MZ_FALSE;
+
+ return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags);
+}
+
+mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags)
+{
+ mz_zip_reader_extract_iter_state *pState;
+ mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
+ mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
+
+ /* Argument sanity check */
+ if ((!pZip) || (!pZip->m_pState))
+ return NULL;
+
+ /* Allocate an iterator status structure */
+ pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state));
+ if (!pState)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ return NULL;
+ }
+
+ /* Fetch file details */
+ if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat))
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+ return NULL;
+ }
+
+ /* Encryption and patch files are not supported. */
+ if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+ return NULL;
+ }
+
+ /* This function only supports decompressing stored and deflate. */
+ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+ return NULL;
+ }
+
+ /* Init state - save args */
+ pState->pZip = pZip;
+ pState->flags = flags;
+
+ /* Init state - reset variables to defaults */
+ pState->status = TINFL_STATUS_DONE;
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ pState->file_crc32 = MZ_CRC32_INIT;
+#endif
+ pState->read_buf_ofs = 0;
+ pState->out_buf_ofs = 0;
+ pState->pRead_buf = NULL;
+ pState->pWrite_buf = NULL;
+ pState->out_blk_remain = 0;
+
+ /* Read and parse the local directory entry. */
+ pState->cur_file_ofs = pState->file_stat.m_local_header_ofs;
+ if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+ return NULL;
+ }
+
+ if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+ return NULL;
+ }
+
+ pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
+ if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+ return NULL;
+ }
+
+ /* Decompress the file either directly from memory or from a file input buffer. */
+ if (pZip->m_pState->m_pMem)
+ {
+ pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs;
+ pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size;
+ pState->comp_remaining = pState->file_stat.m_comp_size;
+ }
+ else
+ {
+ if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)))
+ {
+ /* Decompression required, therefore intermediate read buffer required */
+ pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);
+ if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size)))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+ return NULL;
+ }
+ }
+ else
+ {
+ /* Decompression not required - we will be reading directly into user buffer, no temp buf required */
+ pState->read_buf_size = 0;
+ }
+ pState->read_buf_avail = 0;
+ pState->comp_remaining = pState->file_stat.m_comp_size;
+ }
+
+ if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)))
+ {
+ /* Decompression required, init decompressor */
+ tinfl_init( &pState->inflator );
+
+ /* Allocate write buffer */
+ if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE)))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ if (pState->pRead_buf)
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf);
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+ return NULL;
+ }
+ }
+
+ return pState;
+}
+
+mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags)
+{
+ mz_uint32 file_index;
+
+ /* Locate file index by name */
+ if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))
+ return NULL;
+
+ /* Construct iterator */
+ return mz_zip_reader_extract_iter_new(pZip, file_index, flags);
+}
+
+size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size)
+{
+ size_t copied_to_caller = 0;
+
+ /* Argument sanity check */
+ if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf))
+ return 0;
+
+ if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))
+ {
+ /* The file is stored or the caller has requested the compressed data, calc amount to return. */
+ copied_to_caller = (size_t)MZ_MIN( buf_size, pState->comp_remaining );
+
+ /* Zip is in memory....or requires reading from a file? */
+ if (pState->pZip->m_pState->m_pMem)
+ {
+ /* Copy data to caller's buffer */
+ memcpy( pvBuf, pState->pRead_buf, copied_to_caller );
+ pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller;
+ }
+ else
+ {
+ /* Read directly into caller's buffer */
+ if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller)
+ {
+ /* Failed to read all that was asked for, flag failure and alert user */
+ mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED);
+ pState->status = TINFL_STATUS_FAILED;
+ copied_to_caller = 0;
+ }
+ }
+
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ /* Compute CRC if not returning compressed data only */
+ if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
+ pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller);
+#endif
+
+ /* Advance offsets, dec counters */
+ pState->cur_file_ofs += copied_to_caller;
+ pState->out_buf_ofs += copied_to_caller;
+ pState->comp_remaining -= copied_to_caller;
+ }
+ else
+ {
+ do
+ {
+ /* Calc ptr to write buffer - given current output pos and block size */
+ mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
+
+ /* Calc max output size - given current output pos and block size */
+ size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
+
+ if (!pState->out_blk_remain)
+ {
+ /* Read more data from file if none available (and reading from file) */
+ if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem))
+ {
+ /* Calc read size */
+ pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining);
+ if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail)
+ {
+ mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED);
+ pState->status = TINFL_STATUS_FAILED;
+ break;
+ }
+
+ /* Advance offsets, dec counters */
+ pState->cur_file_ofs += pState->read_buf_avail;
+ pState->comp_remaining -= pState->read_buf_avail;
+ pState->read_buf_ofs = 0;
+ }
+
+ /* Perform decompression */
+ in_buf_size = (size_t)pState->read_buf_avail;
+ pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0);
+ pState->read_buf_avail -= in_buf_size;
+ pState->read_buf_ofs += in_buf_size;
+
+ /* Update current output block size remaining */
+ pState->out_blk_remain = out_buf_size;
+ }
+
+ if (pState->out_blk_remain)
+ {
+ /* Calc amount to return. */
+ size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain );
+
+ /* Copy data to caller's buffer */
+ memcpy( (uint8_t*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy );
+
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ /* Perform CRC */
+ pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy);
+#endif
+
+ /* Decrement data consumed from block */
+ pState->out_blk_remain -= to_copy;
+
+ /* Inc output offset, while performing sanity check */
+ if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size)
+ {
+ mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED);
+ pState->status = TINFL_STATUS_FAILED;
+ break;
+ }
+
+ /* Increment counter of data copied to caller */
+ copied_to_caller += to_copy;
+ }
+ } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) );
+ }
+
+ /* Return how many bytes were copied into user buffer */
+ return copied_to_caller;
+}
+
+mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState)
+{
+ int status;
+
+ /* Argument sanity check */
+ if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState))
+ return MZ_FALSE;
+
+ /* Was decompression completed and requested? */
+ if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
+ {
+ /* Make sure the entire file was decompressed, and check its CRC. */
+ if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size)
+ {
+ mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);
+ pState->status = TINFL_STATUS_FAILED;
+ }
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ else if (pState->file_crc32 != pState->file_stat.m_crc32)
+ {
+ mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED);
+ pState->status = TINFL_STATUS_FAILED;
+ }
+#endif
+ }
+
+ /* Free buffers */
+ if (!pState->pZip->m_pState->m_pMem)
+ pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf);
+ if (pState->pWrite_buf)
+ pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf);
+
+ /* Save status */
+ status = pState->status;
+
+ /* Free context */
+ pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState);
+
+ return status == TINFL_STATUS_DONE;
+}
+
+#ifndef MINIZ_NO_STDIO
+static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n)
+{
+ (void)ofs;
+
+ return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque);
+}
+
+mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags)
+{
+ mz_bool status;
+ mz_zip_archive_file_stat file_stat;
+ MZ_FILE *pFile;
+
+ if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
+ return MZ_FALSE;
+
+ if ((file_stat.m_is_directory) || (!file_stat.m_is_supported))
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);
+
+ pFile = MZ_FOPEN(pDst_filename, "wb");
+ if (!pFile)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);
+
+ status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags);
+
+ if (MZ_FCLOSE(pFile) == EOF)
+ {
+ if (status)
+ mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);
+
+ status = MZ_FALSE;
+ }
+
+#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO)
+ if (status)
+ mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time);
+#endif
+
+ return status;
+}
+
+mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags)
+{
+ mz_uint32 file_index;
+ if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index))
+ return MZ_FALSE;
+
+ return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags);
+}
+
+mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags)
+{
+ mz_zip_archive_file_stat file_stat;
+
+ if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
+ return MZ_FALSE;
+
+ if ((file_stat.m_is_directory) || (!file_stat.m_is_supported))
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);
+
+ return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags);
+}
+
+mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags)
+{
+ mz_uint32 file_index;
+ if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index))
+ return MZ_FALSE;
+
+ return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags);
+}
+#endif /* #ifndef MINIZ_NO_STDIO */
+
+static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
+{
+ mz_uint32 *p = (mz_uint32 *)pOpaque;
+ (void)file_ofs;
+ *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n);
+ return n;
+}
+
+mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags)
+{
+ mz_zip_archive_file_stat file_stat;
+ mz_zip_internal_state *pState;
+ const mz_uint8 *pCentral_dir_header;
+ mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE;
+ mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE;
+ mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
+ mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
+ mz_uint64 local_header_ofs = 0;
+ mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32;
+ mz_uint64 local_header_comp_size, local_header_uncomp_size;
+ mz_uint32 uncomp_crc32 = MZ_CRC32_INIT;
+ mz_bool has_data_descriptor;
+ mz_uint32 local_header_bit_flags;
+
+ mz_zip_array file_data_array;
+ mz_zip_array_init(&file_data_array, 1);
+
+ if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (file_index > pZip->m_total_files)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ pState = pZip->m_pState;
+
+ pCentral_dir_header = mz_zip_get_cdh(pZip, file_index);
+
+ if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir))
+ return MZ_FALSE;
+
+ /* A directory or zero length file */
+ if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size))
+ return MZ_TRUE;
+
+ /* Encryption and patch files are not supported. */
+ if (file_stat.m_is_encrypted)
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);
+
+ /* This function only supports stored and deflate. */
+ if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);
+
+ if (!file_stat.m_is_supported)
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);
+
+ /* Read and parse the local directory entry. */
+ local_header_ofs = file_stat.m_local_header_ofs;
+ if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+
+ if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS);
+ local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
+ local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS);
+ local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS);
+ local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS);
+ local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS);
+ has_data_descriptor = (local_header_bit_flags & 8) != 0;
+
+ if (local_header_filename_len != strlen(file_stat.m_filename))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ goto handle_failure;
+ }
+
+ if (local_header_filename_len)
+ {
+ if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ goto handle_failure;
+ }
+
+ /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */
+ if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
+ goto handle_failure;
+ }
+ }
+
+ if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX)))
+ {
+ mz_uint32 extra_size_remaining = local_header_extra_len;
+ const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p;
+
+ if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ goto handle_failure;
+ }
+
+ do
+ {
+ mz_uint32 field_id, field_data_size, field_total_size;
+
+ if (extra_size_remaining < (sizeof(mz_uint16) * 2))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ goto handle_failure;
+ }
+
+ field_id = MZ_READ_LE16(pExtra_data);
+ field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
+ field_total_size = field_data_size + sizeof(mz_uint16) * 2;
+
+ if (field_total_size > extra_size_remaining)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ goto handle_failure;
+ }
+
+ if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
+ {
+ const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32);
+
+ if (field_data_size < sizeof(mz_uint64) * 2)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ goto handle_failure;
+ }
+
+ local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data);
+ local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64));
+
+ found_zip64_ext_data_in_ldir = MZ_TRUE;
+ break;
+ }
+
+ pExtra_data += field_total_size;
+ extra_size_remaining -= field_total_size;
+ } while (extra_size_remaining);
+ }
+
+ /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */
+ /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */
+ if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32))
+ {
+ mz_uint8 descriptor_buf[32];
+ mz_bool has_id;
+ const mz_uint8 *pSrc;
+ mz_uint32 file_crc32;
+ mz_uint64 comp_size = 0, uncomp_size = 0;
+
+ mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4;
+
+ if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ goto handle_failure;
+ }
+
+ has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID);
+ pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf;
+
+ file_crc32 = MZ_READ_LE32(pSrc);
+
+ if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir))
+ {
+ comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32));
+ uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64));
+ }
+ else
+ {
+ comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32));
+ uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32));
+ }
+
+ if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
+ goto handle_failure;
+ }
+ }
+ else
+ {
+ if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
+ goto handle_failure;
+ }
+ }
+
+ mz_zip_array_clear(pZip, &file_data_array);
+
+ if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0)
+ {
+ if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0))
+ return MZ_FALSE;
+
+ /* 1 more check to be sure, although the extract checks too. */
+ if (uncomp_crc32 != file_stat.m_crc32)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
+ return MZ_FALSE;
+ }
+ }
+
+ return MZ_TRUE;
+
+handle_failure:
+ mz_zip_array_clear(pZip, &file_data_array);
+ return MZ_FALSE;
+}
+
+mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags)
+{
+ mz_zip_internal_state *pState;
+ uint32_t i;
+
+ if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ pState = pZip->m_pState;
+
+ /* Basic sanity checks */
+ if (!pState->m_zip64)
+ {
+ if (pZip->m_total_files > MZ_UINT16_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
+
+ if (pZip->m_archive_size > MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
+ }
+ else
+ {
+ if (pZip->m_total_files >= MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
+
+ if (pState->m_central_dir.m_size >= MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
+ }
+
+ for (i = 0; i < pZip->m_total_files; i++)
+ {
+ if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags)
+ {
+ mz_uint32 found_index;
+ mz_zip_archive_file_stat stat;
+
+ if (!mz_zip_reader_file_stat(pZip, i, &stat))
+ return MZ_FALSE;
+
+ if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index))
+ return MZ_FALSE;
+
+ /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */
+ if (found_index != i)
+ return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);
+ }
+
+ if (!mz_zip_validate_file(pZip, i, flags))
+ return MZ_FALSE;
+ }
+
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr)
+{
+ mz_bool success = MZ_TRUE;
+ mz_zip_archive zip;
+ mz_zip_error actual_err = MZ_ZIP_NO_ERROR;
+
+ if ((!pMem) || (!size))
+ {
+ if (pErr)
+ *pErr = MZ_ZIP_INVALID_PARAMETER;
+ return MZ_FALSE;
+ }
+
+ mz_zip_zero_struct(&zip);
+
+ if (!mz_zip_reader_init_mem(&zip, pMem, size, flags))
+ {
+ if (pErr)
+ *pErr = zip.m_last_error;
+ return MZ_FALSE;
+ }
+
+ if (!mz_zip_validate_archive(&zip, flags))
+ {
+ actual_err = zip.m_last_error;
+ success = MZ_FALSE;
+ }
+
+ if (!mz_zip_reader_end_internal(&zip, success))
+ {
+ if (!actual_err)
+ actual_err = zip.m_last_error;
+ success = MZ_FALSE;
+ }
+
+ if (pErr)
+ *pErr = actual_err;
+
+ return success;
+}
+
+#ifndef MINIZ_NO_STDIO
+mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr)
+{
+ mz_bool success = MZ_TRUE;
+ mz_zip_archive zip;
+ mz_zip_error actual_err = MZ_ZIP_NO_ERROR;
+
+ if (!pFilename)
+ {
+ if (pErr)
+ *pErr = MZ_ZIP_INVALID_PARAMETER;
+ return MZ_FALSE;
+ }
+
+ mz_zip_zero_struct(&zip);
+
+ if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0))
+ {
+ if (pErr)
+ *pErr = zip.m_last_error;
+ return MZ_FALSE;
+ }
+
+ if (!mz_zip_validate_archive(&zip, flags))
+ {
+ actual_err = zip.m_last_error;
+ success = MZ_FALSE;
+ }
+
+ if (!mz_zip_reader_end_internal(&zip, success))
+ {
+ if (!actual_err)
+ actual_err = zip.m_last_error;
+ success = MZ_FALSE;
+ }
+
+ if (pErr)
+ *pErr = actual_err;
+
+ return success;
+}
+#endif /* #ifndef MINIZ_NO_STDIO */
+
+/* ------------------- .ZIP archive writing */
+
+#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
+
+static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v)
+{
+ p[0] = (mz_uint8)v;
+ p[1] = (mz_uint8)(v >> 8);
+}
+static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v)
+{
+ p[0] = (mz_uint8)v;
+ p[1] = (mz_uint8)(v >> 8);
+ p[2] = (mz_uint8)(v >> 16);
+ p[3] = (mz_uint8)(v >> 24);
+}
+static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v)
+{
+ mz_write_le32(p, (mz_uint32)v);
+ mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32));
+}
+
+#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v))
+#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v))
+#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v))
+
+static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
+{
+ mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
+ mz_zip_internal_state *pState = pZip->m_pState;
+ mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size);
+
+ if (!n)
+ return 0;
+
+ /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */
+ if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);
+ return 0;
+ }
+
+ if (new_size > pState->m_mem_capacity)
+ {
+ void *pNew_block;
+ size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity);
+
+ while (new_capacity < new_size)
+ new_capacity *= 2;
+
+ if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity)))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ return 0;
+ }
+
+ pState->m_pMem = pNew_block;
+ pState->m_mem_capacity = new_capacity;
+ }
+ memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n);
+ pState->m_mem_size = (size_t)new_size;
+ return n;
+}
+
+static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error)
+{
+ mz_zip_internal_state *pState;
+ mz_bool status = MZ_TRUE;
+
+ if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)))
+ {
+ if (set_last_error)
+ mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+ return MZ_FALSE;
+ }
+
+ pState = pZip->m_pState;
+ pZip->m_pState = NULL;
+ mz_zip_array_clear(pZip, &pState->m_central_dir);
+ mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);
+ mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);
+
+#ifndef MINIZ_NO_STDIO
+ if (pState->m_pFile)
+ {
+ if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)
+ {
+ if (MZ_FCLOSE(pState->m_pFile) == EOF)
+ {
+ if (set_last_error)
+ mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);
+ status = MZ_FALSE;
+ }
+ }
+
+ pState->m_pFile = NULL;
+ }
+#endif /* #ifndef MINIZ_NO_STDIO */
+
+ if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem))
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem);
+ pState->m_pMem = NULL;
+ }
+
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+ pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;
+ return status;
+}
+
+mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags)
+{
+ mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0;
+
+ if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
+ {
+ if (!pZip->m_pRead)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+ }
+
+ if (pZip->m_file_offset_alignment)
+ {
+ /* Ensure user specified file offset alignment is a power of 2. */
+ if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+ }
+
+ if (!pZip->m_pAlloc)
+ pZip->m_pAlloc = miniz_def_alloc_func;
+ if (!pZip->m_pFree)
+ pZip->m_pFree = miniz_def_free_func;
+ if (!pZip->m_pRealloc)
+ pZip->m_pRealloc = miniz_def_realloc_func;
+
+ pZip->m_archive_size = existing_size;
+ pZip->m_central_directory_file_ofs = 0;
+ pZip->m_total_files = 0;
+
+ if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+
+ memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));
+
+ MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));
+ MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));
+ MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));
+
+ pZip->m_pState->m_zip64 = zip64;
+ pZip->m_pState->m_zip64_has_extended_info_fields = zip64;
+
+ pZip->m_zip_type = MZ_ZIP_TYPE_USER;
+ pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;
+
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size)
+{
+ return mz_zip_writer_init_v2(pZip, existing_size, 0);
+}
+
+mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags)
+{
+ pZip->m_pWrite = mz_zip_heap_write_func;
+ pZip->m_pNeeds_keepalive = NULL;
+
+ if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
+ pZip->m_pRead = mz_zip_mem_read_func;
+
+ pZip->m_pIO_opaque = pZip;
+
+ if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags))
+ return MZ_FALSE;
+
+ pZip->m_zip_type = MZ_ZIP_TYPE_HEAP;
+
+ if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning)))
+ {
+ if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size)))
+ {
+ mz_zip_writer_end_internal(pZip, MZ_FALSE);
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+ pZip->m_pState->m_mem_capacity = initial_allocation_size;
+ }
+
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size)
+{
+ return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0);
+}
+
+#ifndef MINIZ_NO_STDIO
+static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
+{
+ mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
+ mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);
+
+ file_ofs += pZip->m_pState->m_file_archive_start_ofs;
+
+ if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);
+ return 0;
+ }
+
+ return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile);
+}
+
+mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning)
+{
+ return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0);
+}
+
+mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags)
+{
+ MZ_FILE *pFile;
+
+ pZip->m_pWrite = mz_zip_file_write_func;
+ pZip->m_pNeeds_keepalive = NULL;
+
+ if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
+ pZip->m_pRead = mz_zip_file_read_func;
+
+ pZip->m_pIO_opaque = pZip;
+
+ if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags))
+ return MZ_FALSE;
+
+ if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb")))
+ {
+ mz_zip_writer_end(pZip);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);
+ }
+
+ pZip->m_pState->m_pFile = pFile;
+ pZip->m_zip_type = MZ_ZIP_TYPE_FILE;
+
+ if (size_to_reserve_at_beginning)
+ {
+ mz_uint64 cur_ofs = 0;
+ char buf[4096];
+
+ MZ_CLEAR_OBJ(buf);
+
+ do
+ {
+ size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning);
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n)
+ {
+ mz_zip_writer_end(pZip);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+ }
+ cur_ofs += n;
+ size_to_reserve_at_beginning -= n;
+ } while (size_to_reserve_at_beginning);
+ }
+
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags)
+{
+ pZip->m_pWrite = mz_zip_file_write_func;
+ pZip->m_pNeeds_keepalive = NULL;
+
+ if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)
+ pZip->m_pRead = mz_zip_file_read_func;
+
+ pZip->m_pIO_opaque = pZip;
+
+ if (!mz_zip_writer_init_v2(pZip, 0, flags))
+ return MZ_FALSE;
+
+ pZip->m_pState->m_pFile = pFile;
+ pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);
+ pZip->m_zip_type = MZ_ZIP_TYPE_CFILE;
+
+ return MZ_TRUE;
+}
+#endif /* #ifndef MINIZ_NO_STDIO */
+
+mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags)
+{
+ mz_zip_internal_state *pState;
+
+ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (flags & MZ_ZIP_FLAG_WRITE_ZIP64)
+ {
+ /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */
+ if (!pZip->m_pState->m_zip64)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+ }
+
+ /* No sense in trying to write to an archive that's already at the support max size */
+ if (pZip->m_pState->m_zip64)
+ {
+ if (pZip->m_total_files == MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+ }
+ else
+ {
+ if (pZip->m_total_files == MZ_UINT16_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+
+ if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);
+ }
+
+ pState = pZip->m_pState;
+
+ if (pState->m_pFile)
+ {
+#ifdef MINIZ_NO_STDIO
+ (void)pFilename;
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+#else
+ if (pZip->m_pIO_opaque != pZip)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)
+ {
+ if (!pFilename)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */
+ if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile)))
+ {
+ /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */
+ mz_zip_reader_end_internal(pZip, MZ_FALSE);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);
+ }
+ }
+
+ pZip->m_pWrite = mz_zip_file_write_func;
+ pZip->m_pNeeds_keepalive = NULL;
+#endif /* #ifdef MINIZ_NO_STDIO */
+ }
+ else if (pState->m_pMem)
+ {
+ /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */
+ if (pZip->m_pIO_opaque != pZip)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ pState->m_mem_capacity = pState->m_mem_size;
+ pZip->m_pWrite = mz_zip_heap_write_func;
+ pZip->m_pNeeds_keepalive = NULL;
+ }
+ /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */
+ else if (!pZip->m_pWrite)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ /* Start writing new files at the archive's current central directory location. */
+ /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */
+ pZip->m_archive_size = pZip->m_central_directory_file_ofs;
+ pZip->m_central_directory_file_ofs = 0;
+
+ /* Clear the sorted central dir offsets, they aren't useful or maintained now. */
+ /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */
+ /* TODO: We could easily maintain the sorted central directory offsets. */
+ mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets);
+
+ pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;
+
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename)
+{
+ return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0);
+}
+
+/* TODO: pArchive_name is a terrible name here! */
+mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags)
+{
+ return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0);
+}
+
+typedef struct
+{
+ mz_zip_archive *m_pZip;
+ mz_uint64 m_cur_archive_file_ofs;
+ mz_uint64 m_comp_size;
+} mz_zip_writer_add_state;
+
+static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser)
+{
+ mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser;
+ if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len)
+ return MZ_FALSE;
+
+ pState->m_cur_archive_file_ofs += len;
+ pState->m_comp_size += len;
+ return MZ_TRUE;
+}
+
+#define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2)
+#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3)
+static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs)
+{
+ mz_uint8 *pDst = pBuf;
+ mz_uint32 field_size = 0;
+
+ MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID);
+ MZ_WRITE_LE16(pDst + 2, 0);
+ pDst += sizeof(mz_uint16) * 2;
+
+ if (pUncomp_size)
+ {
+ MZ_WRITE_LE64(pDst, *pUncomp_size);
+ pDst += sizeof(mz_uint64);
+ field_size += sizeof(mz_uint64);
+ }
+
+ if (pComp_size)
+ {
+ MZ_WRITE_LE64(pDst, *pComp_size);
+ pDst += sizeof(mz_uint64);
+ field_size += sizeof(mz_uint64);
+ }
+
+ if (pLocal_header_ofs)
+ {
+ MZ_WRITE_LE64(pDst, *pLocal_header_ofs);
+ pDst += sizeof(mz_uint64);
+ field_size += sizeof(mz_uint64);
+ }
+
+ MZ_WRITE_LE16(pBuf + 2, field_size);
+
+ return (mz_uint32)(pDst - pBuf);
+}
+
+static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date)
+{
+ (void)pZip;
+ memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE);
+ MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date);
+ MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32);
+ MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX));
+ MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX));
+ MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size);
+ return MZ_TRUE;
+}
+
+static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst,
+ mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size,
+ mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32,
+ mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date,
+ mz_uint64 local_header_ofs, mz_uint32 ext_attributes)
+{
+ (void)pZip;
+ memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);
+ MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date);
+ MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32);
+ MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX));
+ MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX));
+ MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size);
+ MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size);
+ MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes);
+ MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX));
+ return MZ_TRUE;
+}
+
+static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size,
+ const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size,
+ mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32,
+ mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date,
+ mz_uint64 local_header_ofs, mz_uint32 ext_attributes,
+ const char *user_extra_data, mz_uint user_extra_data_len)
+{
+ mz_zip_internal_state *pState = pZip->m_pState;
+ mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size;
+ size_t orig_central_dir_size = pState->m_central_dir.m_size;
+ mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];
+
+ if (!pZip->m_pState->m_zip64)
+ {
+ if (local_header_ofs > 0xFFFFFFFF)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);
+ }
+
+ /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */
+ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);
+
+ if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, (mz_uint16)(extra_size + user_extra_data_len), comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes))
+ return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
+
+ if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) ||
+ (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) ||
+ (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) ||
+ (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) ||
+ (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) ||
+ (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1)))
+ {
+ /* Try to resize the central directory array back into its original state. */
+ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ return MZ_TRUE;
+}
+
+static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name)
+{
+ /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */
+ if (*pArchive_name == '/')
+ return MZ_FALSE;
+
+ /* Making sure the name does not contain drive letters or DOS style backward slashes is the responsibility of the program using miniz*/
+
+ return MZ_TRUE;
+}
+
+static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip)
+{
+ mz_uint32 n;
+ if (!pZip->m_file_offset_alignment)
+ return 0;
+ n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1));
+ return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1));
+}
+
+static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n)
+{
+ char buf[4096];
+ memset(buf, 0, MZ_MIN(sizeof(buf), n));
+ while (n)
+ {
+ mz_uint32 s = MZ_MIN(sizeof(buf), n);
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ cur_file_ofs += s;
+ n -= s;
+ }
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
+ mz_uint64 uncomp_size, mz_uint32 uncomp_crc32)
+{
+ return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0);
+}
+
+mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size,
+ mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified,
+ const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len)
+{
+ mz_uint16 method = 0, dos_time = 0, dos_date = 0;
+ mz_uint level, ext_attributes = 0, num_alignment_padding_bytes;
+ mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0;
+ size_t archive_name_size;
+ mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
+ tdefl_compressor *pComp = NULL;
+ mz_bool store_data_uncompressed;
+ mz_zip_internal_state *pState;
+ mz_uint8 *pExtra_data = NULL;
+ mz_uint32 extra_size = 0;
+ mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE];
+ mz_uint16 bit_flags = 0;
+
+ if ((int)level_and_flags < 0)
+ level_and_flags = MZ_DEFAULT_LEVEL;
+
+ if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
+ bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;
+
+ if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME))
+ bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8;
+
+ level = level_and_flags & 0xF;
+ store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA));
+
+ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ pState = pZip->m_pState;
+
+ if (pState->m_zip64)
+ {
+ if (pZip->m_total_files == MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+ }
+ else
+ {
+ if (pZip->m_total_files == MZ_UINT16_MAX)
+ {
+ pState->m_zip64 = MZ_TRUE;
+ /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */
+ }
+ if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF))
+ {
+ pState->m_zip64 = MZ_TRUE;
+ /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
+ }
+ }
+
+ if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (!mz_zip_writer_validate_archive_name(pArchive_name))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);
+
+#ifndef MINIZ_NO_TIME
+ if (last_modified != NULL)
+ {
+ mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date);
+ }
+ else
+ {
+ MZ_TIME_T cur_time;
+ time(&cur_time);
+ mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date);
+ }
+#endif /* #ifndef MINIZ_NO_TIME */
+
+ if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
+ {
+ uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size);
+ uncomp_size = buf_size;
+ if (uncomp_size <= 3)
+ {
+ level = 0;
+ store_data_uncompressed = MZ_TRUE;
+ }
+ }
+
+ archive_name_size = strlen(pArchive_name);
+ if (archive_name_size > MZ_UINT16_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);
+
+ num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);
+
+ /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */
+ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);
+
+ if (!pState->m_zip64)
+ {
+ /* Bail early if the archive would obviously become too large */
+ if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size
+ + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len +
+ pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len
+ + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF)
+ {
+ pState->m_zip64 = MZ_TRUE;
+ /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
+ }
+ }
+
+ if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/'))
+ {
+ /* Set DOS Subdirectory attribute bit. */
+ ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG;
+
+ /* Subdirectories cannot contain data. */
+ if ((buf_size) || (uncomp_size))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+ }
+
+ /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */
+ if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1)))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+
+ if ((!store_data_uncompressed) && (buf_size))
+ {
+ if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor))))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes))
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+ return MZ_FALSE;
+ }
+
+ local_dir_header_ofs += num_alignment_padding_bytes;
+ if (pZip->m_file_offset_alignment)
+ {
+ MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0);
+ }
+ cur_archive_file_ofs += num_alignment_padding_bytes;
+
+ MZ_CLEAR_OBJ(local_dir_header);
+
+ if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
+ {
+ method = MZ_DEFLATED;
+ }
+
+ if (pState->m_zip64)
+ {
+ if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX)
+ {
+ pExtra_data = extra_data;
+ extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
+ (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
+ }
+
+ if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, bit_flags, dos_time, dos_date))
+ return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ cur_archive_file_ofs += sizeof(local_dir_header);
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+ }
+ cur_archive_file_ofs += archive_name_size;
+
+ if (pExtra_data != NULL)
+ {
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ cur_archive_file_ofs += extra_size;
+ }
+ }
+ else
+ {
+ if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX))
+ return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
+ if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date))
+ return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ cur_archive_file_ofs += sizeof(local_dir_header);
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+ }
+ cur_archive_file_ofs += archive_name_size;
+ }
+
+ if (user_extra_data_len > 0)
+ {
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ cur_archive_file_ofs += user_extra_data_len;
+ }
+
+ if (store_data_uncompressed)
+ {
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size)
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+ }
+
+ cur_archive_file_ofs += buf_size;
+ comp_size = buf_size;
+ }
+ else if (buf_size)
+ {
+ mz_zip_writer_add_state state;
+
+ state.m_pZip = pZip;
+ state.m_cur_archive_file_ofs = cur_archive_file_ofs;
+ state.m_comp_size = 0;
+
+ if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) ||
+ (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE))
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+ return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED);
+ }
+
+ comp_size = state.m_comp_size;
+ cur_archive_file_ofs = state.m_cur_archive_file_ofs;
+ }
+
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+ pComp = NULL;
+
+ if (uncomp_size)
+ {
+ mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64];
+ mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32;
+
+ MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR);
+
+ MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID);
+ MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32);
+ if (pExtra_data == NULL)
+ {
+ if (comp_size > MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
+
+ MZ_WRITE_LE32(local_dir_footer + 8, comp_size);
+ MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size);
+ }
+ else
+ {
+ MZ_WRITE_LE64(local_dir_footer + 8, comp_size);
+ MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size);
+ local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64;
+ }
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size)
+ return MZ_FALSE;
+
+ cur_archive_file_ofs += local_dir_footer_size;
+ }
+
+ if (pExtra_data != NULL)
+ {
+ extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
+ (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
+ }
+
+ if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment,
+ comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes,
+ user_extra_data_central, user_extra_data_central_len))
+ return MZ_FALSE;
+
+ pZip->m_total_files++;
+ pZip->m_archive_size = cur_archive_file_ofs;
+
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
+ const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len)
+{
+ mz_uint16 gen_flags = (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) ? 0 : MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;
+ mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes;
+ mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0;
+ mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0;
+ size_t archive_name_size;
+ mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
+ mz_uint8 *pExtra_data = NULL;
+ mz_uint32 extra_size = 0;
+ mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE];
+ mz_zip_internal_state *pState;
+ mz_uint64 file_ofs = 0, cur_archive_header_file_ofs;
+
+ if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME))
+ gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8;
+
+ if ((int)level_and_flags < 0)
+ level_and_flags = MZ_DEFAULT_LEVEL;
+ level = level_and_flags & 0xF;
+
+ /* Sanity checks */
+ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ pState = pZip->m_pState;
+
+ if ((!pState->m_zip64) && (max_size > MZ_UINT32_MAX))
+ {
+ /* Source file is too large for non-zip64 */
+ /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
+ pState->m_zip64 = MZ_TRUE;
+ }
+
+ /* We could support this, but why? */
+ if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (!mz_zip_writer_validate_archive_name(pArchive_name))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);
+
+ if (pState->m_zip64)
+ {
+ if (pZip->m_total_files == MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+ }
+ else
+ {
+ if (pZip->m_total_files == MZ_UINT16_MAX)
+ {
+ pState->m_zip64 = MZ_TRUE;
+ /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */
+ }
+ }
+
+ archive_name_size = strlen(pArchive_name);
+ if (archive_name_size > MZ_UINT16_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);
+
+ num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);
+
+ /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */
+ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);
+
+ if (!pState->m_zip64)
+ {
+ /* Bail early if the archive would obviously become too large */
+ if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE
+ + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024
+ + MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF)
+ {
+ pState->m_zip64 = MZ_TRUE;
+ /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */
+ }
+ }
+
+#ifndef MINIZ_NO_TIME
+ if (pFile_time)
+ {
+ mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date);
+ }
+#endif
+
+ if (max_size <= 3)
+ level = 0;
+
+ if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes))
+ {
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+ }
+
+ cur_archive_file_ofs += num_alignment_padding_bytes;
+ local_dir_header_ofs = cur_archive_file_ofs;
+
+ if (pZip->m_file_offset_alignment)
+ {
+ MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0);
+ }
+
+ if (max_size && level)
+ {
+ method = MZ_DEFLATED;
+ }
+
+ MZ_CLEAR_OBJ(local_dir_header);
+ if (pState->m_zip64)
+ {
+ if (max_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX)
+ {
+ pExtra_data = extra_data;
+ if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)
+ extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
+ (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL,
+ (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
+ else
+ extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, NULL,
+ NULL,
+ (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
+ }
+
+ if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, gen_flags, dos_time, dos_date))
+ return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ cur_archive_file_ofs += sizeof(local_dir_header);
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
+ {
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+ }
+
+ cur_archive_file_ofs += archive_name_size;
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ cur_archive_file_ofs += extra_size;
+ }
+ else
+ {
+ if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX))
+ return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
+ if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date))
+ return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ cur_archive_file_ofs += sizeof(local_dir_header);
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
+ {
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+ }
+
+ cur_archive_file_ofs += archive_name_size;
+ }
+
+ if (user_extra_data_len > 0)
+ {
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ cur_archive_file_ofs += user_extra_data_len;
+ }
+
+ if (max_size)
+ {
+ void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE);
+ if (!pRead_buf)
+ {
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ if (!level)
+ {
+ while (1)
+ {
+ size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE);
+ if (n == 0)
+ break;
+
+ if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size))
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ }
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+ }
+ file_ofs += n;
+ uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n);
+ cur_archive_file_ofs += n;
+ }
+ uncomp_size = file_ofs;
+ comp_size = uncomp_size;
+ }
+ else
+ {
+ mz_bool result = MZ_FALSE;
+ mz_zip_writer_add_state state;
+ tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor));
+ if (!pComp)
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ state.m_pZip = pZip;
+ state.m_cur_archive_file_ofs = cur_archive_file_ofs;
+ state.m_comp_size = 0;
+
+ if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY)
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+ return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
+ }
+
+ for (;;)
+ {
+ tdefl_status status;
+ tdefl_flush flush = TDEFL_NO_FLUSH;
+
+ size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE);
+ if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size))
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ break;
+ }
+
+ file_ofs += n;
+ uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n);
+
+ if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque))
+ flush = TDEFL_FULL_FLUSH;
+
+ if (n == 0)
+ flush = TDEFL_FINISH;
+
+ status = tdefl_compress_buffer(pComp, pRead_buf, n, flush);
+ if (status == TDEFL_STATUS_DONE)
+ {
+ result = MZ_TRUE;
+ break;
+ }
+ else if (status != TDEFL_STATUS_OKAY)
+ {
+ mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED);
+ break;
+ }
+ }
+
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+
+ if (!result)
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+ return MZ_FALSE;
+ }
+
+ uncomp_size = file_ofs;
+ comp_size = state.m_comp_size;
+ cur_archive_file_ofs = state.m_cur_archive_file_ofs;
+ }
+
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+ }
+
+ if (!(level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE))
+ {
+ mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64];
+ mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32;
+
+ MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID);
+ MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32);
+ if (pExtra_data == NULL)
+ {
+ if (comp_size > MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
+
+ MZ_WRITE_LE32(local_dir_footer + 8, comp_size);
+ MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size);
+ }
+ else
+ {
+ MZ_WRITE_LE64(local_dir_footer + 8, comp_size);
+ MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size);
+ local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64;
+ }
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size)
+ return MZ_FALSE;
+
+ cur_archive_file_ofs += local_dir_footer_size;
+ }
+
+ if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)
+ {
+ if (pExtra_data != NULL)
+ {
+ extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
+ (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
+ }
+
+ if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header,
+ (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len),
+ (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : uncomp_size,
+ (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : comp_size,
+ uncomp_crc32, method, gen_flags, dos_time, dos_date))
+ return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);
+
+ cur_archive_header_file_ofs = local_dir_header_ofs;
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ if (pExtra_data != NULL)
+ {
+ cur_archive_header_file_ofs += sizeof(local_dir_header);
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
+ {
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+ }
+
+ cur_archive_header_file_ofs += archive_name_size;
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, extra_data, extra_size) != extra_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ cur_archive_header_file_ofs += extra_size;
+ }
+ }
+
+ if (pExtra_data != NULL)
+ {
+ extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,
+ (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);
+ }
+
+ if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size,
+ uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes,
+ user_extra_data_central, user_extra_data_central_len))
+ return MZ_FALSE;
+
+ pZip->m_total_files++;
+ pZip->m_archive_size = cur_archive_file_ofs;
+
+ return MZ_TRUE;
+}
+
+#ifndef MINIZ_NO_STDIO
+
+static size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
+{
+ MZ_FILE *pSrc_file = (MZ_FILE *)pOpaque;
+ mz_int64 cur_ofs = MZ_FTELL64(pSrc_file);
+
+ if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pSrc_file, (mz_int64)file_ofs, SEEK_SET))))
+ return 0;
+
+ return MZ_FREAD(pBuf, 1, n, pSrc_file);
+}
+
+mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
+ const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len)
+{
+ return mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, max_size, pFile_time, pComment, comment_size, level_and_flags,
+ user_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len);
+}
+
+mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)
+{
+ MZ_FILE *pSrc_file = NULL;
+ mz_uint64 uncomp_size = 0;
+ MZ_TIME_T file_modified_time;
+ MZ_TIME_T *pFile_time = NULL;
+ mz_bool status;
+
+ memset(&file_modified_time, 0, sizeof(file_modified_time));
+
+#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO)
+ pFile_time = &file_modified_time;
+ if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time))
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED);
+#endif
+
+ pSrc_file = MZ_FOPEN(pSrc_filename, "rb");
+ if (!pSrc_file)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);
+
+ MZ_FSEEK64(pSrc_file, 0, SEEK_END);
+ uncomp_size = MZ_FTELL64(pSrc_file);
+ MZ_FSEEK64(pSrc_file, 0, SEEK_SET);
+
+ status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0);
+
+ MZ_FCLOSE(pSrc_file);
+
+ return status;
+}
+#endif /* #ifndef MINIZ_NO_STDIO */
+
+static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, uint32_t ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start)
+{
+ /* + 64 should be enough for any new zip64 data */
+ if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+
+ mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE);
+
+ if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start))
+ {
+ mz_uint8 new_ext_block[64];
+ mz_uint8 *pDst = new_ext_block;
+ mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID);
+ mz_write_le16(pDst + sizeof(mz_uint16), 0);
+ pDst += sizeof(mz_uint16) * 2;
+
+ if (pUncomp_size)
+ {
+ mz_write_le64(pDst, *pUncomp_size);
+ pDst += sizeof(mz_uint64);
+ }
+
+ if (pComp_size)
+ {
+ mz_write_le64(pDst, *pComp_size);
+ pDst += sizeof(mz_uint64);
+ }
+
+ if (pLocal_header_ofs)
+ {
+ mz_write_le64(pDst, *pLocal_header_ofs);
+ pDst += sizeof(mz_uint64);
+ }
+
+ if (pDisk_start)
+ {
+ mz_write_le32(pDst, *pDisk_start);
+ pDst += sizeof(mz_uint32);
+ }
+
+ mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2));
+
+ if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ if ((pExt) && (ext_len))
+ {
+ mz_uint32 extra_size_remaining = ext_len;
+ const mz_uint8 *pExtra_data = pExt;
+
+ do
+ {
+ mz_uint32 field_id, field_data_size, field_total_size;
+
+ if (extra_size_remaining < (sizeof(mz_uint16) * 2))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ field_id = MZ_READ_LE16(pExtra_data);
+ field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
+ field_total_size = field_data_size + sizeof(mz_uint16) * 2;
+
+ if (field_total_size > extra_size_remaining)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
+ {
+ if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ pExtra_data += field_total_size;
+ extra_size_remaining -= field_total_size;
+ } while (extra_size_remaining);
+ }
+
+ return MZ_TRUE;
+}
+
+/* TODO: This func is now pretty freakin complex due to zip64, split it up? */
+mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index)
+{
+ mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size;
+ mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs;
+ mz_uint64 cur_src_file_ofs, cur_dst_file_ofs;
+ mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];
+ mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
+ mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];
+ size_t orig_central_dir_size;
+ mz_zip_internal_state *pState;
+ void *pBuf;
+ const mz_uint8 *pSrc_central_header;
+ mz_zip_archive_file_stat src_file_stat;
+ mz_uint32 src_filename_len, src_comment_len, src_ext_len;
+ mz_uint32 local_header_filename_size, local_header_extra_len;
+ mz_uint64 local_header_comp_size, local_header_uncomp_size;
+ mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE;
+
+ /* Sanity checks */
+ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ pState = pZip->m_pState;
+
+ /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */
+ if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ /* Get pointer to the source central dir header and crack it */
+ if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index)))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+ src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS);
+ src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS);
+ src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len;
+
+ /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */
+ if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);
+
+ num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);
+
+ if (!pState->m_zip64)
+ {
+ if (pZip->m_total_files == MZ_UINT16_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+ }
+ else
+ {
+ /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */
+ if (pZip->m_total_files == MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+ }
+
+ if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL))
+ return MZ_FALSE;
+
+ cur_src_file_ofs = src_file_stat.m_local_header_ofs;
+ cur_dst_file_ofs = pZip->m_archive_size;
+
+ /* Read the source archive's local dir header */
+ if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+
+ if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+
+ cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;
+
+ /* Compute the total size we need to copy (filename+extra data+compressed data) */
+ local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS);
+ local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
+ local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS);
+ local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS);
+ src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size;
+
+ /* Try to find a zip64 extended information field */
+ if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX)))
+ {
+ mz_zip_array file_data_array;
+ const mz_uint8 *pExtra_data;
+ mz_uint32 extra_size_remaining = local_header_extra_len;
+
+ mz_zip_array_init(&file_data_array, 1);
+ if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE))
+ {
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len)
+ {
+ mz_zip_array_clear(pZip, &file_data_array);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ }
+
+ pExtra_data = (const mz_uint8 *)file_data_array.m_p;
+
+ do
+ {
+ mz_uint32 field_id, field_data_size, field_total_size;
+
+ if (extra_size_remaining < (sizeof(mz_uint16) * 2))
+ {
+ mz_zip_array_clear(pZip, &file_data_array);
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ }
+
+ field_id = MZ_READ_LE16(pExtra_data);
+ field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));
+ field_total_size = field_data_size + sizeof(mz_uint16) * 2;
+
+ if (field_total_size > extra_size_remaining)
+ {
+ mz_zip_array_clear(pZip, &file_data_array);
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ }
+
+ if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)
+ {
+ const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32);
+
+ if (field_data_size < sizeof(mz_uint64) * 2)
+ {
+ mz_zip_array_clear(pZip, &file_data_array);
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
+ }
+
+ local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data);
+ local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */
+
+ found_zip64_ext_data_in_ldir = MZ_TRUE;
+ break;
+ }
+
+ pExtra_data += field_total_size;
+ extra_size_remaining -= field_total_size;
+ } while (extra_size_remaining);
+
+ mz_zip_array_clear(pZip, &file_data_array);
+ }
+
+ if (!pState->m_zip64)
+ {
+ /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */
+ /* We also check when the archive is finalized so this doesn't need to be perfect. */
+ mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) +
+ pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64;
+
+ if (approx_new_archive_size >= MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
+ }
+
+ /* Write dest archive padding */
+ if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes))
+ return MZ_FALSE;
+
+ cur_dst_file_ofs += num_alignment_padding_bytes;
+
+ local_dir_header_ofs = cur_dst_file_ofs;
+ if (pZip->m_file_offset_alignment)
+ {
+ MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0);
+ }
+
+ /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;
+
+ /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */
+ if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining)))))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+
+ while (src_archive_bytes_remaining)
+ {
+ n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining);
+ if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n)
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ }
+ cur_src_file_ofs += n;
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+ }
+ cur_dst_file_ofs += n;
+
+ src_archive_bytes_remaining -= n;
+ }
+
+ /* Now deal with the optional data descriptor */
+ bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS);
+ if (bit_flags & 8)
+ {
+ /* Copy data descriptor */
+ if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir))
+ {
+ /* src is zip64, dest must be zip64 */
+
+ /* name uint32_t's */
+ /* id 1 (optional in zip64?) */
+ /* crc 1 */
+ /* comp_size 2 */
+ /* uncomp_size 2 */
+ if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6))
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ }
+
+ n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5);
+ }
+ else
+ {
+ /* src is NOT zip64 */
+ mz_bool has_id;
+
+ if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4)
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
+ }
+
+ has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID);
+
+ if (pZip->m_pState->m_zip64)
+ {
+ /* dest is zip64, so upgrade the data descriptor */
+ const mz_uint32 *pSrc_descriptor = (const mz_uint32 *)((const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0));
+ const mz_uint32 src_crc32 = pSrc_descriptor[0];
+ const mz_uint64 src_comp_size = pSrc_descriptor[1];
+ const mz_uint64 src_uncomp_size = pSrc_descriptor[2];
+
+ mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID);
+ mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32);
+ mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size);
+ mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size);
+
+ n = sizeof(mz_uint32) * 6;
+ }
+ else
+ {
+ /* dest is NOT zip64, just copy it as-is */
+ n = sizeof(mz_uint32) * (has_id ? 4 : 3);
+ }
+ }
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)
+ {
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+ }
+
+ cur_src_file_ofs += n;
+ cur_dst_file_ofs += n;
+ }
+ pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+
+ /* Finally, add the new central dir header */
+ orig_central_dir_size = pState->m_central_dir.m_size;
+
+ memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);
+
+ if (pState->m_zip64)
+ {
+ /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */
+ const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len;
+ mz_zip_array new_ext_block;
+
+ mz_zip_array_init(&new_ext_block, sizeof(mz_uint8));
+
+ MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX);
+ MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX);
+ MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX);
+
+ if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL))
+ {
+ mz_zip_array_clear(pZip, &new_ext_block);
+ return MZ_FALSE;
+ }
+
+ MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size);
+
+ if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE))
+ {
+ mz_zip_array_clear(pZip, &new_ext_block);
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len))
+ {
+ mz_zip_array_clear(pZip, &new_ext_block);
+ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size))
+ {
+ mz_zip_array_clear(pZip, &new_ext_block);
+ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len))
+ {
+ mz_zip_array_clear(pZip, &new_ext_block);
+ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ mz_zip_array_clear(pZip, &new_ext_block);
+ }
+ else
+ {
+ /* sanity checks */
+ if (cur_dst_file_ofs > MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
+
+ if (local_dir_header_ofs >= MZ_UINT32_MAX)
+ return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);
+
+ MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs);
+
+ if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE))
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+
+ if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size))
+ {
+ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+ }
+
+ /* This shouldn't trigger unless we screwed up during the initial sanity checks */
+ if (pState->m_central_dir.m_size >= MZ_UINT32_MAX)
+ {
+ /* TODO: Support central dirs >= 32-bits in size */
+ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
+ return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);
+ }
+
+ n = (mz_uint32)orig_central_dir_size;
+ if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1))
+ {
+ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
+ return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);
+ }
+
+ pZip->m_total_files++;
+ pZip->m_archive_size = cur_dst_file_ofs;
+
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip)
+{
+ mz_zip_internal_state *pState;
+ mz_uint64 central_dir_ofs, central_dir_size;
+ mz_uint8 hdr[256];
+
+ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ pState = pZip->m_pState;
+
+ if (pState->m_zip64)
+ {
+ if ((pZip->m_total_files > MZ_UINT32_MAX) || (pState->m_central_dir.m_size >= MZ_UINT32_MAX))
+ return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+ }
+ else
+ {
+ if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX))
+ return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);
+ }
+
+ central_dir_ofs = 0;
+ central_dir_size = 0;
+ if (pZip->m_total_files)
+ {
+ /* Write central directory */
+ central_dir_ofs = pZip->m_archive_size;
+ central_dir_size = pState->m_central_dir.m_size;
+ pZip->m_central_directory_file_ofs = central_dir_ofs;
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ pZip->m_archive_size += central_dir_size;
+ }
+
+ if (pState->m_zip64)
+ {
+ /* Write zip64 end of central directory header */
+ mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size;
+
+ MZ_CLEAR_OBJ(hdr);
+ MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG);
+ MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64));
+ MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */
+ MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D);
+ MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files);
+ MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files);
+ MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size);
+ MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs);
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE;
+
+ /* Write zip64 end of central directory locator */
+ MZ_CLEAR_OBJ(hdr);
+ MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG);
+ MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr);
+ MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1);
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+ pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE;
+ }
+
+ /* Write end of central directory record */
+ MZ_CLEAR_OBJ(hdr);
+ MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG);
+ MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files));
+ MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files));
+ MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size));
+ MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs));
+
+ if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);
+
+#ifndef MINIZ_NO_STDIO
+ if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF))
+ return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);
+#endif /* #ifndef MINIZ_NO_STDIO */
+
+ pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE;
+
+ pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED;
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize)
+{
+ if ((!ppBuf) || (!pSize))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ *ppBuf = NULL;
+ *pSize = 0;
+
+ if ((!pZip) || (!pZip->m_pState))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (pZip->m_pWrite != mz_zip_heap_write_func)
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ if (!mz_zip_writer_finalize_archive(pZip))
+ return MZ_FALSE;
+
+ *ppBuf = pZip->m_pState->m_pMem;
+ *pSize = pZip->m_pState->m_mem_size;
+ pZip->m_pState->m_pMem = NULL;
+ pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0;
+
+ return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_end(mz_zip_archive *pZip)
+{
+ return mz_zip_writer_end_internal(pZip, MZ_TRUE);
+}
+
+#ifndef MINIZ_NO_STDIO
+mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)
+{
+ return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL);
+}
+
+mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr)
+{
+ mz_bool status, created_new_archive = MZ_FALSE;
+ mz_zip_archive zip_archive;
+ struct MZ_FILE_STAT_STRUCT file_stat;
+ mz_zip_error actual_err = MZ_ZIP_NO_ERROR;
+
+ mz_zip_zero_struct(&zip_archive);
+ if ((int)level_and_flags < 0)
+ level_and_flags = MZ_DEFAULT_LEVEL;
+
+ if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION))
+ {
+ if (pErr)
+ *pErr = MZ_ZIP_INVALID_PARAMETER;
+ return MZ_FALSE;
+ }
+
+ if (!mz_zip_writer_validate_archive_name(pArchive_name))
+ {
+ if (pErr)
+ *pErr = MZ_ZIP_INVALID_FILENAME;
+ return MZ_FALSE;
+ }
+
+ /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */
+ /* So be sure to compile with _LARGEFILE64_SOURCE 1 */
+ if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0)
+ {
+ /* Create a new archive. */
+ if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags))
+ {
+ if (pErr)
+ *pErr = zip_archive.m_last_error;
+ return MZ_FALSE;
+ }
+
+ created_new_archive = MZ_TRUE;
+ }
+ else
+ {
+ /* Append to an existing archive. */
+ if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0))
+ {
+ if (pErr)
+ *pErr = zip_archive.m_last_error;
+ return MZ_FALSE;
+ }
+
+ if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags))
+ {
+ if (pErr)
+ *pErr = zip_archive.m_last_error;
+
+ mz_zip_reader_end_internal(&zip_archive, MZ_FALSE);
+
+ return MZ_FALSE;
+ }
+ }
+
+ status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0);
+ actual_err = zip_archive.m_last_error;
+
+ /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */
+ if (!mz_zip_writer_finalize_archive(&zip_archive))
+ {
+ if (!actual_err)
+ actual_err = zip_archive.m_last_error;
+
+ status = MZ_FALSE;
+ }
+
+ if (!mz_zip_writer_end_internal(&zip_archive, status))
+ {
+ if (!actual_err)
+ actual_err = zip_archive.m_last_error;
+
+ status = MZ_FALSE;
+ }
+
+ if ((!status) && (created_new_archive))
+ {
+ /* It's a new archive and something went wrong, so just delete it. */
+ int ignoredStatus = MZ_DELETE_FILE(pZip_filename);
+ (void)ignoredStatus;
+ }
+
+ if (pErr)
+ *pErr = actual_err;
+
+ return status;
+}
+
+void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr)
+{
+ mz_uint32 file_index;
+ mz_zip_archive zip_archive;
+ void *p = NULL;
+
+ if (pSize)
+ *pSize = 0;
+
+ if ((!pZip_filename) || (!pArchive_name))
+ {
+ if (pErr)
+ *pErr = MZ_ZIP_INVALID_PARAMETER;
+
+ return NULL;
+ }
+
+ mz_zip_zero_struct(&zip_archive);
+ if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0))
+ {
+ if (pErr)
+ *pErr = zip_archive.m_last_error;
+
+ return NULL;
+ }
+
+ if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index))
+ {
+ p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags);
+ }
+
+ mz_zip_reader_end_internal(&zip_archive, p != NULL);
+
+ if (pErr)
+ *pErr = zip_archive.m_last_error;
+
+ return p;
+}
+
+void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags)
+{
+ return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL);
+}
+
+#endif /* #ifndef MINIZ_NO_STDIO */
+
+#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */
+
+/* ------------------- Misc utils */
+
+mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip)
+{
+ return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID;
+}
+
+mz_zip_type mz_zip_get_type(mz_zip_archive *pZip)
+{
+ return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID;
+}
+
+mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num)
+{
+ mz_zip_error prev_err;
+
+ if (!pZip)
+ return MZ_ZIP_INVALID_PARAMETER;
+
+ prev_err = pZip->m_last_error;
+
+ pZip->m_last_error = err_num;
+ return prev_err;
+}
+
+mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip)
+{
+ if (!pZip)
+ return MZ_ZIP_INVALID_PARAMETER;
+
+ return pZip->m_last_error;
+}
+
+mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip)
+{
+ return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR);
+}
+
+mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip)
+{
+ mz_zip_error prev_err;
+
+ if (!pZip)
+ return MZ_ZIP_INVALID_PARAMETER;
+
+ prev_err = pZip->m_last_error;
+
+ pZip->m_last_error = MZ_ZIP_NO_ERROR;
+ return prev_err;
+}
+
+const char *mz_zip_get_error_string(mz_zip_error mz_err)
+{
+ switch (mz_err)
+ {
+ case MZ_ZIP_NO_ERROR:
+ return "no error";
+ case MZ_ZIP_UNDEFINED_ERROR:
+ return "undefined error";
+ case MZ_ZIP_TOO_MANY_FILES:
+ return "too many files";
+ case MZ_ZIP_FILE_TOO_LARGE:
+ return "file too large";
+ case MZ_ZIP_UNSUPPORTED_METHOD:
+ return "unsupported method";
+ case MZ_ZIP_UNSUPPORTED_ENCRYPTION:
+ return "unsupported encryption";
+ case MZ_ZIP_UNSUPPORTED_FEATURE:
+ return "unsupported feature";
+ case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR:
+ return "failed finding central directory";
+ case MZ_ZIP_NOT_AN_ARCHIVE:
+ return "not a ZIP archive";
+ case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED:
+ return "invalid header or archive is corrupted";
+ case MZ_ZIP_UNSUPPORTED_MULTIDISK:
+ return "unsupported multidisk archive";
+ case MZ_ZIP_DECOMPRESSION_FAILED:
+ return "decompression failed or archive is corrupted";
+ case MZ_ZIP_COMPRESSION_FAILED:
+ return "compression failed";
+ case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE:
+ return "unexpected decompressed size";
+ case MZ_ZIP_CRC_CHECK_FAILED:
+ return "CRC-32 check failed";
+ case MZ_ZIP_UNSUPPORTED_CDIR_SIZE:
+ return "unsupported central directory size";
+ case MZ_ZIP_ALLOC_FAILED:
+ return "allocation failed";
+ case MZ_ZIP_FILE_OPEN_FAILED:
+ return "file open failed";
+ case MZ_ZIP_FILE_CREATE_FAILED:
+ return "file create failed";
+ case MZ_ZIP_FILE_WRITE_FAILED:
+ return "file write failed";
+ case MZ_ZIP_FILE_READ_FAILED:
+ return "file read failed";
+ case MZ_ZIP_FILE_CLOSE_FAILED:
+ return "file close failed";
+ case MZ_ZIP_FILE_SEEK_FAILED:
+ return "file seek failed";
+ case MZ_ZIP_FILE_STAT_FAILED:
+ return "file stat failed";
+ case MZ_ZIP_INVALID_PARAMETER:
+ return "invalid parameter";
+ case MZ_ZIP_INVALID_FILENAME:
+ return "invalid filename";
+ case MZ_ZIP_BUF_TOO_SMALL:
+ return "buffer too small";
+ case MZ_ZIP_INTERNAL_ERROR:
+ return "internal error";
+ case MZ_ZIP_FILE_NOT_FOUND:
+ return "file not found";
+ case MZ_ZIP_ARCHIVE_TOO_LARGE:
+ return "archive is too large";
+ case MZ_ZIP_VALIDATION_FAILED:
+ return "validation failed";
+ case MZ_ZIP_WRITE_CALLBACK_FAILED:
+ return "write calledback failed";
+ default:
+ break;
+ }
+
+ return "unknown error";
+}
+
+/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */
+mz_bool mz_zip_is_zip64(mz_zip_archive *pZip)
+{
+ if ((!pZip) || (!pZip->m_pState))
+ return MZ_FALSE;
+
+ return pZip->m_pState->m_zip64;
+}
+
+size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip)
+{
+ if ((!pZip) || (!pZip->m_pState))
+ return 0;
+
+ return pZip->m_pState->m_central_dir.m_size;
+}
+
+mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip)
+{
+ return pZip ? pZip->m_total_files : 0;
+}
+
+mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip)
+{
+ if (!pZip)
+ return 0;
+ return pZip->m_archive_size;
+}
+
+mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip)
+{
+ if ((!pZip) || (!pZip->m_pState))
+ return 0;
+ return pZip->m_pState->m_file_archive_start_ofs;
+}
+
+MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip)
+{
+ if ((!pZip) || (!pZip->m_pState))
+ return 0;
+ return pZip->m_pState->m_pFile;
+}
+
+size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n)
+{
+ if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead))
+ return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+
+ return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n);
+}
+
+mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size)
+{
+ mz_uint n;
+ const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);
+ if (!p)
+ {
+ if (filename_buf_size)
+ pFilename[0] = '\0';
+ mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
+ return 0;
+ }
+ n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+ if (filename_buf_size)
+ {
+ n = MZ_MIN(n, filename_buf_size - 1);
+ memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n);
+ pFilename[n] = '\0';
+ }
+ return n + 1;
+}
+
+mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat)
+{
+ return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL);
+}
+
+mz_bool mz_zip_end(mz_zip_archive *pZip)
+{
+ if (!pZip)
+ return MZ_FALSE;
+
+ if (pZip->m_zip_mode == MZ_ZIP_MODE_READING)
+ return mz_zip_reader_end(pZip);
+#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
+ else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))
+ return mz_zip_writer_end(pZip);
+#endif
+
+ return MZ_FALSE;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/
diff --git a/luaclib/src/lz/miniz.h b/luaclib/src/lz/miniz.h
new file mode 100644
index 00000000..4191145c
--- /dev/null
+++ b/luaclib/src/lz/miniz.h
@@ -0,0 +1,1346 @@
+#define MINIZ_EXPORT
+/* miniz.c 2.2.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing
+ See "unlicense" statement at the end of this file.
+ Rich Geldreich , last updated Oct. 13, 2013
+ Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt
+
+ Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define
+ MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros).
+
+ * Low-level Deflate/Inflate implementation notes:
+
+ Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or
+ greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses
+ approximately as well as zlib.
+
+ Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function
+ coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory
+ block large enough to hold the entire file.
+
+ The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation.
+
+ * zlib-style API notes:
+
+ miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in
+ zlib replacement in many apps:
+ The z_stream struct, optional memory allocation callbacks
+ deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound
+ inflateInit/inflateInit2/inflate/inflateReset/inflateEnd
+ compress, compress2, compressBound, uncompress
+ CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines.
+ Supports raw deflate streams or standard zlib streams with adler-32 checking.
+
+ Limitations:
+ The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries.
+ I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but
+ there are no guarantees that miniz.c pulls this off perfectly.
+
+ * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by
+ Alex Evans. Supports 1-4 bytes/pixel images.
+
+ * ZIP archive API notes:
+
+ The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to
+ get the job done with minimal fuss. There are simple API's to retrieve file information, read files from
+ existing archives, create new archives, append new files to existing archives, or clone archive data from
+ one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h),
+ or you can specify custom file read/write callbacks.
+
+ - Archive reading: Just call this function to read a single file from a disk archive:
+
+ void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name,
+ size_t *pSize, mz_uint zip_flags);
+
+ For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central
+ directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files.
+
+ - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file:
+
+ int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);
+
+ The locate operation can optionally check file comments too, which (as one example) can be used to identify
+ multiple versions of the same file in an archive. This function uses a simple linear search through the central
+ directory, so it's not very fast.
+
+ Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and
+ retrieve detailed info on each file by calling mz_zip_reader_file_stat().
+
+ - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data
+ to disk and builds an exact image of the central directory in memory. The central directory image is written
+ all at once at the end of the archive file when the archive is finalized.
+
+ The archive writer can optionally align each file's local header and file data to any power of 2 alignment,
+ which can be useful when the archive will be read from optical media. Also, the writer supports placing
+ arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still
+ readable by any ZIP tool.
+
+ - Archive appending: The simple way to add a single file to an archive is to call this function:
+
+ mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name,
+ const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);
+
+ The archive will be created if it doesn't already exist, otherwise it'll be appended to.
+ Note the appending is done in-place and is not an atomic operation, so if something goes wrong
+ during the operation it's possible the archive could be left without a central directory (although the local
+ file headers and file data will be fine, so the archive will be recoverable).
+
+ For more complex archive modification scenarios:
+ 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to
+ preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the
+ compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and
+ you're done. This is safe but requires a bunch of temporary disk space or heap memory.
+
+ 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(),
+ append new files as needed, then finalize the archive which will write an updated central directory to the
+ original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a
+ possibility that the archive's central directory could be lost with this method if anything goes wrong, though.
+
+ - ZIP archive support limitations:
+ No spanning support. Extraction functions can only handle unencrypted, stored or deflated files.
+ Requires streams capable of seeking.
+
+ * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the
+ below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it.
+
+ * Important: For best perf. be sure to customize the below macros for your target platform:
+ #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
+ #define MINIZ_LITTLE_ENDIAN 1
+ #define MINIZ_HAS_64BIT_REGISTERS 1
+
+ * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz
+ uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files
+ (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes).
+*/
+#pragma once
+
+
+
+/* Defines to completely disable specific portions of miniz.c:
+ If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */
+
+/* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */
+/*#define MINIZ_NO_STDIO */
+
+/* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */
+/* get/set file times, and the C run-time funcs that get/set times won't be called. */
+/* The current downside is the times written to your archives will be from 1979. */
+/*#define MINIZ_NO_TIME */
+
+/* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */
+/*#define MINIZ_NO_ARCHIVE_APIS */
+
+/* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */
+/*#define MINIZ_NO_ARCHIVE_WRITING_APIS */
+
+/* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */
+/*#define MINIZ_NO_ZLIB_APIS */
+
+/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */
+/*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */
+
+/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc.
+ Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc
+ callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user
+ functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */
+/*#define MINIZ_NO_MALLOC */
+
+#if defined(__TINYC__) && (defined(__linux) || defined(__linux__))
+/* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */
+#define MINIZ_NO_TIME
+#endif
+
+#include
+
+#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS)
+#include
+#endif
+
+#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__)
+/* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */
+#define MINIZ_X86_OR_X64_CPU 1
+#else
+#define MINIZ_X86_OR_X64_CPU 0
+#endif
+
+#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU
+/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */
+#define MINIZ_LITTLE_ENDIAN 1
+#else
+#define MINIZ_LITTLE_ENDIAN 0
+#endif
+
+/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */
+#if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES)
+#if MINIZ_X86_OR_X64_CPU
+/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */
+#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
+#define MINIZ_UNALIGNED_USE_MEMCPY
+#else
+#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0
+#endif
+#endif
+
+#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__)
+/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */
+#define MINIZ_HAS_64BIT_REGISTERS 1
+#else
+#define MINIZ_HAS_64BIT_REGISTERS 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ------------------- zlib-style API Definitions. */
+
+/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */
+typedef unsigned long mz_ulong;
+
+/* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */
+MINIZ_EXPORT void mz_free(void *p);
+
+#define MZ_ADLER32_INIT (1)
+/* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */
+MINIZ_EXPORT mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len);
+
+#define MZ_CRC32_INIT (0)
+/* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */
+MINIZ_EXPORT mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len);
+
+/* Compression strategies. */
+enum
+{
+ MZ_DEFAULT_STRATEGY = 0,
+ MZ_FILTERED = 1,
+ MZ_HUFFMAN_ONLY = 2,
+ MZ_RLE = 3,
+ MZ_FIXED = 4
+};
+
+/* Method */
+#define MZ_DEFLATED 8
+
+/* Heap allocation callbacks.
+Note that mz_alloc_func parameter types purposely differ from zlib's: items/size is size_t, not unsigned long. */
+typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size);
+typedef void (*mz_free_func)(void *opaque, void *address);
+typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size);
+
+/* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */
+enum
+{
+ MZ_NO_COMPRESSION = 0,
+ MZ_BEST_SPEED = 1,
+ MZ_BEST_COMPRESSION = 9,
+ MZ_UBER_COMPRESSION = 10,
+ MZ_DEFAULT_LEVEL = 6,
+ MZ_DEFAULT_COMPRESSION = -1
+};
+
+#define MZ_VERSION "10.2.0"
+#define MZ_VERNUM 0xA100
+#define MZ_VER_MAJOR 10
+#define MZ_VER_MINOR 2
+#define MZ_VER_REVISION 0
+#define MZ_VER_SUBREVISION 0
+
+#ifndef MINIZ_NO_ZLIB_APIS
+
+/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */
+enum
+{
+ MZ_NO_FLUSH = 0,
+ MZ_PARTIAL_FLUSH = 1,
+ MZ_SYNC_FLUSH = 2,
+ MZ_FULL_FLUSH = 3,
+ MZ_FINISH = 4,
+ MZ_BLOCK = 5
+};
+
+/* Return status codes. MZ_PARAM_ERROR is non-standard. */
+enum
+{
+ MZ_OK = 0,
+ MZ_STREAM_END = 1,
+ MZ_NEED_DICT = 2,
+ MZ_ERRNO = -1,
+ MZ_STREAM_ERROR = -2,
+ MZ_DATA_ERROR = -3,
+ MZ_MEM_ERROR = -4,
+ MZ_BUF_ERROR = -5,
+ MZ_VERSION_ERROR = -6,
+ MZ_PARAM_ERROR = -10000
+};
+
+/* Window bits */
+#define MZ_DEFAULT_WINDOW_BITS 15
+
+struct mz_internal_state;
+
+/* Compression/decompression stream struct. */
+typedef struct mz_stream_s
+{
+ const unsigned char *next_in; /* pointer to next byte to read */
+ unsigned int avail_in; /* number of bytes available at next_in */
+ mz_ulong total_in; /* total number of bytes consumed so far */
+
+ unsigned char *next_out; /* pointer to next byte to write */
+ unsigned int avail_out; /* number of bytes that can be written to next_out */
+ mz_ulong total_out; /* total number of bytes produced so far */
+
+ char *msg; /* error msg (unused) */
+ struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */
+
+ mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */
+ mz_free_func zfree; /* optional heap free function (defaults to free) */
+ void *opaque; /* heap alloc function user pointer */
+
+ int data_type; /* data_type (unused) */
+ mz_ulong adler; /* adler32 of the source or uncompressed data */
+ mz_ulong reserved; /* not used */
+} mz_stream;
+
+typedef mz_stream *mz_streamp;
+
+/* Returns the version string of miniz.c. */
+MINIZ_EXPORT const char *mz_version(void);
+
+/* mz_deflateInit() initializes a compressor with default options: */
+/* Parameters: */
+/* pStream must point to an initialized mz_stream struct. */
+/* level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */
+/* level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */
+/* (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */
+/* Return values: */
+/* MZ_OK on success. */
+/* MZ_STREAM_ERROR if the stream is bogus. */
+/* MZ_PARAM_ERROR if the input parameters are bogus. */
+/* MZ_MEM_ERROR on out of memory. */
+MINIZ_EXPORT int mz_deflateInit(mz_streamp pStream, int level);
+
+/* mz_deflateInit2() is like mz_deflate(), except with more control: */
+/* Additional parameters: */
+/* method must be MZ_DEFLATED */
+/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */
+/* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */
+MINIZ_EXPORT int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy);
+
+/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */
+MINIZ_EXPORT int mz_deflateReset(mz_streamp pStream);
+
+/* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */
+/* Parameters: */
+/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */
+/* flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */
+/* Return values: */
+/* MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */
+/* MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */
+/* MZ_STREAM_ERROR if the stream is bogus. */
+/* MZ_PARAM_ERROR if one of the parameters is invalid. */
+/* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */
+MINIZ_EXPORT int mz_deflate(mz_streamp pStream, int flush);
+
+/* mz_deflateEnd() deinitializes a compressor: */
+/* Return values: */
+/* MZ_OK on success. */
+/* MZ_STREAM_ERROR if the stream is bogus. */
+MINIZ_EXPORT int mz_deflateEnd(mz_streamp pStream);
+
+/* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */
+MINIZ_EXPORT mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len);
+
+/* Single-call compression functions mz_compress() and mz_compress2(): */
+/* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */
+MINIZ_EXPORT int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);
+MINIZ_EXPORT int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level);
+
+/* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */
+MINIZ_EXPORT mz_ulong mz_compressBound(mz_ulong source_len);
+
+/* Initializes a decompressor. */
+MINIZ_EXPORT int mz_inflateInit(mz_streamp pStream);
+
+/* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */
+/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */
+MINIZ_EXPORT int mz_inflateInit2(mz_streamp pStream, int window_bits);
+
+/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */
+MINIZ_EXPORT int mz_inflateReset(mz_streamp pStream);
+
+/* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */
+/* Parameters: */
+/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */
+/* flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */
+/* On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */
+/* MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */
+/* Return values: */
+/* MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */
+/* MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */
+/* MZ_STREAM_ERROR if the stream is bogus. */
+/* MZ_DATA_ERROR if the deflate stream is invalid. */
+/* MZ_PARAM_ERROR if one of the parameters is invalid. */
+/* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */
+/* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */
+MINIZ_EXPORT int mz_inflate(mz_streamp pStream, int flush);
+
+/* Deinitializes a decompressor. */
+MINIZ_EXPORT int mz_inflateEnd(mz_streamp pStream);
+
+/* Single-call decompression. */
+/* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */
+MINIZ_EXPORT int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);
+MINIZ_EXPORT int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len);
+
+/* Returns a string description of the specified error code, or NULL if the error code is invalid. */
+MINIZ_EXPORT const char *mz_error(int err);
+
+/* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */
+/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */
+#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES
+typedef unsigned char Byte;
+typedef unsigned int uInt;
+typedef mz_ulong uLong;
+typedef Byte Bytef;
+typedef uInt uIntf;
+typedef char charf;
+typedef int intf;
+typedef void *voidpf;
+typedef uLong uLongf;
+typedef void *voidp;
+typedef void *const voidpc;
+#define Z_NULL 0
+#define Z_NO_FLUSH MZ_NO_FLUSH
+#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH
+#define Z_SYNC_FLUSH MZ_SYNC_FLUSH
+#define Z_FULL_FLUSH MZ_FULL_FLUSH
+#define Z_FINISH MZ_FINISH
+#define Z_BLOCK MZ_BLOCK
+#define Z_OK MZ_OK
+#define Z_STREAM_END MZ_STREAM_END
+#define Z_NEED_DICT MZ_NEED_DICT
+#define Z_ERRNO MZ_ERRNO
+#define Z_STREAM_ERROR MZ_STREAM_ERROR
+#define Z_DATA_ERROR MZ_DATA_ERROR
+#define Z_MEM_ERROR MZ_MEM_ERROR
+#define Z_BUF_ERROR MZ_BUF_ERROR
+#define Z_VERSION_ERROR MZ_VERSION_ERROR
+#define Z_PARAM_ERROR MZ_PARAM_ERROR
+#define Z_NO_COMPRESSION MZ_NO_COMPRESSION
+#define Z_BEST_SPEED MZ_BEST_SPEED
+#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION
+#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION
+#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY
+#define Z_FILTERED MZ_FILTERED
+#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY
+#define Z_RLE MZ_RLE
+#define Z_FIXED MZ_FIXED
+#define Z_DEFLATED MZ_DEFLATED
+#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS
+#define alloc_func mz_alloc_func
+#define free_func mz_free_func
+#define internal_state mz_internal_state
+#define z_stream mz_stream
+#define deflateInit mz_deflateInit
+#define deflateInit2 mz_deflateInit2
+#define deflateReset mz_deflateReset
+#define deflate mz_deflate
+#define deflateEnd mz_deflateEnd
+#define deflateBound mz_deflateBound
+#define compress mz_compress
+#define compress2 mz_compress2
+#define compressBound mz_compressBound
+#define inflateInit mz_inflateInit
+#define inflateInit2 mz_inflateInit2
+#define inflateReset mz_inflateReset
+#define inflate mz_inflate
+#define inflateEnd mz_inflateEnd
+#define uncompress mz_uncompress
+#define uncompress2 mz_uncompress2
+#define crc32 mz_crc32
+#define adler32 mz_adler32
+#define MAX_WBITS 15
+#define MAX_MEM_LEVEL 9
+#define zError mz_error
+#define ZLIB_VERSION MZ_VERSION
+#define ZLIB_VERNUM MZ_VERNUM
+#define ZLIB_VER_MAJOR MZ_VER_MAJOR
+#define ZLIB_VER_MINOR MZ_VER_MINOR
+#define ZLIB_VER_REVISION MZ_VER_REVISION
+#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION
+#define zlibVersion mz_version
+#define zlib_version mz_version()
+#endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */
+
+#endif /* MINIZ_NO_ZLIB_APIS */
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+
+
+#pragma once
+#include
+#include
+#include
+#include
+
+
+
+/* ------------------- Types and macros */
+typedef unsigned char mz_uint8;
+typedef signed short mz_int16;
+typedef unsigned short mz_uint16;
+typedef unsigned int mz_uint32;
+typedef unsigned int mz_uint;
+typedef int64_t mz_int64;
+typedef uint64_t mz_uint64;
+typedef int mz_bool;
+
+#define MZ_FALSE (0)
+#define MZ_TRUE (1)
+
+/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */
+#ifdef _MSC_VER
+#define MZ_MACRO_END while (0, 0)
+#else
+#define MZ_MACRO_END while (0)
+#endif
+
+#ifdef MINIZ_NO_STDIO
+#define MZ_FILE void *
+#else
+#include
+#define MZ_FILE FILE
+#endif /* #ifdef MINIZ_NO_STDIO */
+
+#ifdef MINIZ_NO_TIME
+typedef struct mz_dummy_time_t_tag
+{
+ int m_dummy;
+} mz_dummy_time_t;
+#define MZ_TIME_T mz_dummy_time_t
+#else
+#define MZ_TIME_T time_t
+#endif
+
+#define MZ_ASSERT(x) assert(x)
+
+#if !defined(MINIZ_NO_MALLOC)
+#define MZ_MALLOC(x) malloc(x)
+#define MZ_FREE(x) free(x)
+#define MZ_REALLOC(p, x) realloc(p, x)
+#endif
+
+#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b))
+#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj))
+
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
+#define MZ_READ_LE16(p) *((const mz_uint16 *)(p))
+#define MZ_READ_LE32(p) *((const mz_uint32 *)(p))
+#else
+#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U))
+#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U))
+#endif
+
+#define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U))
+
+#ifdef _MSC_VER
+#define MZ_FORCEINLINE __forceinline
+#elif defined(__GNUC__)
+#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__))
+#else
+#define MZ_FORCEINLINE inline
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size);
+extern MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address);
+extern MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size);
+
+#define MZ_UINT16_MAX (0xFFFFU)
+#define MZ_UINT32_MAX (0xFFFFFFFFU)
+
+#ifdef __cplusplus
+}
+#endif
+ #pragma once
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/* ------------------- Low-level Compression API Definitions */
+
+/* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */
+#define TDEFL_LESS_MEMORY 0
+
+/* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */
+/* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */
+enum
+{
+ TDEFL_HUFFMAN_ONLY = 0,
+ TDEFL_DEFAULT_MAX_PROBES = 128,
+ TDEFL_MAX_PROBES_MASK = 0xFFF
+};
+
+/* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */
+/* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */
+/* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */
+/* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */
+/* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */
+/* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */
+/* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */
+/* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */
+/* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */
+enum
+{
+ TDEFL_WRITE_ZLIB_HEADER = 0x01000,
+ TDEFL_COMPUTE_ADLER32 = 0x02000,
+ TDEFL_GREEDY_PARSING_FLAG = 0x04000,
+ TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000,
+ TDEFL_RLE_MATCHES = 0x10000,
+ TDEFL_FILTER_MATCHES = 0x20000,
+ TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000,
+ TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000
+};
+
+/* High level compression functions: */
+/* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */
+/* On entry: */
+/* pSrc_buf, src_buf_len: Pointer and size of source block to compress. */
+/* flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */
+/* On return: */
+/* Function returns a pointer to the compressed data, or NULL on failure. */
+/* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */
+/* The caller must free() the returned block when it's no longer needed. */
+MINIZ_EXPORT void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);
+
+/* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */
+/* Returns 0 on failure. */
+MINIZ_EXPORT size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);
+
+/* Compresses an image to a compressed PNG file in memory. */
+/* On entry: */
+/* pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */
+/* The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */
+/* level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */
+/* If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */
+/* On return: */
+/* Function returns a pointer to the compressed data, or NULL on failure. */
+/* *pLen_out will be set to the size of the PNG image file. */
+/* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */
+MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip);
+MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out);
+
+/* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */
+typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser);
+
+/* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */
+MINIZ_EXPORT mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);
+
+enum
+{
+ TDEFL_MAX_HUFF_TABLES = 3,
+ TDEFL_MAX_HUFF_SYMBOLS_0 = 288,
+ TDEFL_MAX_HUFF_SYMBOLS_1 = 32,
+ TDEFL_MAX_HUFF_SYMBOLS_2 = 19,
+ TDEFL_LZ_DICT_SIZE = 32768,
+ TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1,
+ TDEFL_MIN_MATCH_LEN = 3,
+ TDEFL_MAX_MATCH_LEN = 258
+};
+
+/* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */
+#if TDEFL_LESS_MEMORY
+enum
+{
+ TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024,
+ TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10,
+ TDEFL_MAX_HUFF_SYMBOLS = 288,
+ TDEFL_LZ_HASH_BITS = 12,
+ TDEFL_LEVEL1_HASH_SIZE_MASK = 4095,
+ TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3,
+ TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS
+};
+#else
+enum
+{
+ TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024,
+ TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10,
+ TDEFL_MAX_HUFF_SYMBOLS = 288,
+ TDEFL_LZ_HASH_BITS = 15,
+ TDEFL_LEVEL1_HASH_SIZE_MASK = 4095,
+ TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3,
+ TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS
+};
+#endif
+
+/* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */
+typedef enum {
+ TDEFL_STATUS_BAD_PARAM = -2,
+ TDEFL_STATUS_PUT_BUF_FAILED = -1,
+ TDEFL_STATUS_OKAY = 0,
+ TDEFL_STATUS_DONE = 1
+} tdefl_status;
+
+/* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */
+typedef enum {
+ TDEFL_NO_FLUSH = 0,
+ TDEFL_SYNC_FLUSH = 2,
+ TDEFL_FULL_FLUSH = 3,
+ TDEFL_FINISH = 4
+} tdefl_flush;
+
+/* tdefl's compression state structure. */
+typedef struct
+{
+ tdefl_put_buf_func_ptr m_pPut_buf_func;
+ void *m_pPut_buf_user;
+ mz_uint m_flags, m_max_probes[2];
+ int m_greedy_parsing;
+ mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size;
+ mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end;
+ mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer;
+ mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish;
+ tdefl_status m_prev_return_status;
+ const void *m_pIn_buf;
+ void *m_pOut_buf;
+ size_t *m_pIn_buf_size, *m_pOut_buf_size;
+ tdefl_flush m_flush;
+ const mz_uint8 *m_pSrc;
+ size_t m_src_buf_left, m_out_buf_ofs;
+ mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1];
+ mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
+ mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
+ mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
+ mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE];
+ mz_uint16 m_next[TDEFL_LZ_DICT_SIZE];
+ mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE];
+ mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE];
+} tdefl_compressor;
+
+/* Initializes the compressor. */
+/* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */
+/* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */
+/* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */
+/* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */
+MINIZ_EXPORT tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);
+
+/* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */
+MINIZ_EXPORT tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush);
+
+/* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */
+/* tdefl_compress_buffer() always consumes the entire input buffer. */
+MINIZ_EXPORT tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush);
+
+MINIZ_EXPORT tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d);
+MINIZ_EXPORT mz_uint32 tdefl_get_adler32(tdefl_compressor *d);
+
+/* Create tdefl_compress() flags given zlib-style compression parameters. */
+/* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */
+/* window_bits may be -15 (raw deflate) or 15 (zlib) */
+/* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */
+MINIZ_EXPORT mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy);
+
+#ifndef MINIZ_NO_MALLOC
+/* Allocate the tdefl_compressor structure in C so that */
+/* non-C language bindings to tdefl_ API don't need to worry about */
+/* structure size and allocation mechanism. */
+MINIZ_EXPORT tdefl_compressor *tdefl_compressor_alloc(void);
+MINIZ_EXPORT void tdefl_compressor_free(tdefl_compressor *pComp);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+ #pragma once
+
+/* ------------------- Low-level Decompression API Definitions */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/* Decompression flags used by tinfl_decompress(). */
+/* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */
+/* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */
+/* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */
+/* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */
+enum
+{
+ TINFL_FLAG_PARSE_ZLIB_HEADER = 1,
+ TINFL_FLAG_HAS_MORE_INPUT = 2,
+ TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4,
+ TINFL_FLAG_COMPUTE_ADLER32 = 8
+};
+
+/* High level decompression functions: */
+/* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */
+/* On entry: */
+/* pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */
+/* On return: */
+/* Function returns a pointer to the decompressed data, or NULL on failure. */
+/* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */
+/* The caller must call mz_free() on the returned block when it's no longer needed. */
+MINIZ_EXPORT void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);
+
+/* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */
+/* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */
+#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1))
+MINIZ_EXPORT size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);
+
+/* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */
+/* Returns 1 on success or 0 on failure. */
+typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser);
+MINIZ_EXPORT int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);
+
+struct tinfl_decompressor_tag;
+typedef struct tinfl_decompressor_tag tinfl_decompressor;
+
+#ifndef MINIZ_NO_MALLOC
+/* Allocate the tinfl_decompressor structure in C so that */
+/* non-C language bindings to tinfl_ API don't need to worry about */
+/* structure size and allocation mechanism. */
+MINIZ_EXPORT tinfl_decompressor *tinfl_decompressor_alloc(void);
+MINIZ_EXPORT void tinfl_decompressor_free(tinfl_decompressor *pDecomp);
+#endif
+
+/* Max size of LZ dictionary. */
+#define TINFL_LZ_DICT_SIZE 32768
+
+/* Return status. */
+typedef enum {
+ /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */
+ /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */
+ /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */
+ TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4,
+
+ /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */
+ TINFL_STATUS_BAD_PARAM = -3,
+
+ /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */
+ TINFL_STATUS_ADLER32_MISMATCH = -2,
+
+ /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */
+ TINFL_STATUS_FAILED = -1,
+
+ /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */
+
+ /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */
+ /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */
+ TINFL_STATUS_DONE = 0,
+
+ /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */
+ /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */
+ /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */
+ TINFL_STATUS_NEEDS_MORE_INPUT = 1,
+
+ /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */
+ /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */
+ /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */
+ /* so I may need to add some code to address this. */
+ TINFL_STATUS_HAS_MORE_OUTPUT = 2
+} tinfl_status;
+
+/* Initializes the decompressor to its initial state. */
+#define tinfl_init(r) \
+ do \
+ { \
+ (r)->m_state = 0; \
+ } \
+ MZ_MACRO_END
+#define tinfl_get_adler32(r) (r)->m_check_adler32
+
+/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */
+/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */
+MINIZ_EXPORT tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags);
+
+/* Internal/private bits follow. */
+enum
+{
+ TINFL_MAX_HUFF_TABLES = 3,
+ TINFL_MAX_HUFF_SYMBOLS_0 = 288,
+ TINFL_MAX_HUFF_SYMBOLS_1 = 32,
+ TINFL_MAX_HUFF_SYMBOLS_2 = 19,
+ TINFL_FAST_LOOKUP_BITS = 10,
+ TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS
+};
+
+typedef struct
+{
+ mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0];
+ mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2];
+} tinfl_huff_table;
+
+#if MINIZ_HAS_64BIT_REGISTERS
+#define TINFL_USE_64BIT_BITBUF 1
+#else
+#define TINFL_USE_64BIT_BITBUF 0
+#endif
+
+#if TINFL_USE_64BIT_BITBUF
+typedef mz_uint64 tinfl_bit_buf_t;
+#define TINFL_BITBUF_SIZE (64)
+#else
+typedef mz_uint32 tinfl_bit_buf_t;
+#define TINFL_BITBUF_SIZE (32)
+#endif
+
+struct tinfl_decompressor_tag
+{
+ mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES];
+ tinfl_bit_buf_t m_bit_buf;
+ size_t m_dist_from_out_buf_start;
+ tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES];
+ mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137];
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#pragma once
+
+
+/* ------------------- ZIP archive reading/writing */
+
+#ifndef MINIZ_NO_ARCHIVE_APIS
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum
+{
+ /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */
+ MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024,
+ MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512,
+ MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512
+};
+
+typedef struct
+{
+ /* Central directory file index. */
+ mz_uint32 m_file_index;
+
+ /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */
+ mz_uint64 m_central_dir_ofs;
+
+ /* These fields are copied directly from the zip's central dir. */
+ mz_uint16 m_version_made_by;
+ mz_uint16 m_version_needed;
+ mz_uint16 m_bit_flag;
+ mz_uint16 m_method;
+
+#ifndef MINIZ_NO_TIME
+ MZ_TIME_T m_time;
+#endif
+
+ /* CRC-32 of uncompressed data. */
+ mz_uint32 m_crc32;
+
+ /* File's compressed size. */
+ mz_uint64 m_comp_size;
+
+ /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */
+ mz_uint64 m_uncomp_size;
+
+ /* Zip internal and external file attributes. */
+ mz_uint16 m_internal_attr;
+ mz_uint32 m_external_attr;
+
+ /* Entry's local header file offset in bytes. */
+ mz_uint64 m_local_header_ofs;
+
+ /* Size of comment in bytes. */
+ mz_uint32 m_comment_size;
+
+ /* MZ_TRUE if the entry appears to be a directory. */
+ mz_bool m_is_directory;
+
+ /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */
+ mz_bool m_is_encrypted;
+
+ /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */
+ mz_bool m_is_supported;
+
+ /* Filename. If string ends in '/' it's a subdirectory entry. */
+ /* Guaranteed to be zero terminated, may be truncated to fit. */
+ char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE];
+
+ /* Comment field. */
+ /* Guaranteed to be zero terminated, may be truncated to fit. */
+ char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE];
+
+} mz_zip_archive_file_stat;
+
+typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n);
+typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n);
+typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque);
+
+struct mz_zip_internal_state_tag;
+typedef struct mz_zip_internal_state_tag mz_zip_internal_state;
+
+typedef enum {
+ MZ_ZIP_MODE_INVALID = 0,
+ MZ_ZIP_MODE_READING = 1,
+ MZ_ZIP_MODE_WRITING = 2,
+ MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3
+} mz_zip_mode;
+
+typedef enum {
+ MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100,
+ MZ_ZIP_FLAG_IGNORE_PATH = 0x0200,
+ MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400,
+ MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800,
+ MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */
+ MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */
+ MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */
+ MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000,
+ MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000,
+ /*After adding a compressed file, seek back
+ to local file header and set the correct sizes*/
+ MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE = 0x20000
+} mz_zip_flags;
+
+typedef enum {
+ MZ_ZIP_TYPE_INVALID = 0,
+ MZ_ZIP_TYPE_USER,
+ MZ_ZIP_TYPE_MEMORY,
+ MZ_ZIP_TYPE_HEAP,
+ MZ_ZIP_TYPE_FILE,
+ MZ_ZIP_TYPE_CFILE,
+ MZ_ZIP_TOTAL_TYPES
+} mz_zip_type;
+
+/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */
+typedef enum {
+ MZ_ZIP_NO_ERROR = 0,
+ MZ_ZIP_UNDEFINED_ERROR,
+ MZ_ZIP_TOO_MANY_FILES,
+ MZ_ZIP_FILE_TOO_LARGE,
+ MZ_ZIP_UNSUPPORTED_METHOD,
+ MZ_ZIP_UNSUPPORTED_ENCRYPTION,
+ MZ_ZIP_UNSUPPORTED_FEATURE,
+ MZ_ZIP_FAILED_FINDING_CENTRAL_DIR,
+ MZ_ZIP_NOT_AN_ARCHIVE,
+ MZ_ZIP_INVALID_HEADER_OR_CORRUPTED,
+ MZ_ZIP_UNSUPPORTED_MULTIDISK,
+ MZ_ZIP_DECOMPRESSION_FAILED,
+ MZ_ZIP_COMPRESSION_FAILED,
+ MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE,
+ MZ_ZIP_CRC_CHECK_FAILED,
+ MZ_ZIP_UNSUPPORTED_CDIR_SIZE,
+ MZ_ZIP_ALLOC_FAILED,
+ MZ_ZIP_FILE_OPEN_FAILED,
+ MZ_ZIP_FILE_CREATE_FAILED,
+ MZ_ZIP_FILE_WRITE_FAILED,
+ MZ_ZIP_FILE_READ_FAILED,
+ MZ_ZIP_FILE_CLOSE_FAILED,
+ MZ_ZIP_FILE_SEEK_FAILED,
+ MZ_ZIP_FILE_STAT_FAILED,
+ MZ_ZIP_INVALID_PARAMETER,
+ MZ_ZIP_INVALID_FILENAME,
+ MZ_ZIP_BUF_TOO_SMALL,
+ MZ_ZIP_INTERNAL_ERROR,
+ MZ_ZIP_FILE_NOT_FOUND,
+ MZ_ZIP_ARCHIVE_TOO_LARGE,
+ MZ_ZIP_VALIDATION_FAILED,
+ MZ_ZIP_WRITE_CALLBACK_FAILED,
+ MZ_ZIP_TOTAL_ERRORS
+} mz_zip_error;
+
+typedef struct
+{
+ mz_uint64 m_archive_size;
+ mz_uint64 m_central_directory_file_ofs;
+
+ /* We only support up to UINT32_MAX files in zip64 mode. */
+ mz_uint32 m_total_files;
+ mz_zip_mode m_zip_mode;
+ mz_zip_type m_zip_type;
+ mz_zip_error m_last_error;
+
+ mz_uint64 m_file_offset_alignment;
+
+ mz_alloc_func m_pAlloc;
+ mz_free_func m_pFree;
+ mz_realloc_func m_pRealloc;
+ void *m_pAlloc_opaque;
+
+ mz_file_read_func m_pRead;
+ mz_file_write_func m_pWrite;
+ mz_file_needs_keepalive m_pNeeds_keepalive;
+ void *m_pIO_opaque;
+
+ mz_zip_internal_state *m_pState;
+
+} mz_zip_archive;
+
+typedef struct
+{
+ mz_zip_archive *pZip;
+ mz_uint flags;
+
+ int status;
+#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS
+ mz_uint file_crc32;
+#endif
+ mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs;
+ mz_zip_archive_file_stat file_stat;
+ void *pRead_buf;
+ void *pWrite_buf;
+
+ size_t out_blk_remain;
+
+ tinfl_decompressor inflator;
+
+} mz_zip_reader_extract_iter_state;
+
+/* -------- ZIP reading */
+
+/* Inits a ZIP archive reader. */
+/* These functions read and validate the archive's central directory. */
+MINIZ_EXPORT mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags);
+
+MINIZ_EXPORT mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags);
+
+#ifndef MINIZ_NO_STDIO
+/* Read a archive from a disk file. */
+/* file_start_ofs is the file offset where the archive actually begins, or 0. */
+/* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */
+MINIZ_EXPORT mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags);
+MINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size);
+
+/* Read an archive from an already opened FILE, beginning at the current file position. */
+/* The archive is assumed to be archive_size bytes long. If archive_size is 0, then the entire rest of the file is assumed to contain the archive. */
+/* The FILE will NOT be closed when mz_zip_reader_end() is called. */
+MINIZ_EXPORT mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags);
+#endif
+
+/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */
+MINIZ_EXPORT mz_bool mz_zip_reader_end(mz_zip_archive *pZip);
+
+/* -------- ZIP reading or writing */
+
+/* Clears a mz_zip_archive struct to all zeros. */
+/* Important: This must be done before passing the struct to any mz_zip functions. */
+MINIZ_EXPORT void mz_zip_zero_struct(mz_zip_archive *pZip);
+
+MINIZ_EXPORT mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip);
+MINIZ_EXPORT mz_zip_type mz_zip_get_type(mz_zip_archive *pZip);
+
+/* Returns the total number of files in the archive. */
+MINIZ_EXPORT mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip);
+
+MINIZ_EXPORT mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip);
+MINIZ_EXPORT mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip);
+MINIZ_EXPORT MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip);
+
+/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */
+MINIZ_EXPORT size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n);
+
+/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */
+/* Note that the m_last_error functionality is not thread safe. */
+MINIZ_EXPORT mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num);
+MINIZ_EXPORT mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip);
+MINIZ_EXPORT mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip);
+MINIZ_EXPORT mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip);
+MINIZ_EXPORT const char *mz_zip_get_error_string(mz_zip_error mz_err);
+
+/* MZ_TRUE if the archive file entry is a directory entry. */
+MINIZ_EXPORT mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index);
+
+/* MZ_TRUE if the file is encrypted/strong encrypted. */
+MINIZ_EXPORT mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index);
+
+/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */
+MINIZ_EXPORT mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index);
+
+/* Retrieves the filename of an archive file entry. */
+/* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */
+MINIZ_EXPORT mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size);
+
+/* Attempts to locates a file in the archive's central directory. */
+/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */
+/* Returns -1 if the file cannot be found. */
+MINIZ_EXPORT int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);
+MINIZ_EXPORT mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index);
+
+/* Returns detailed information about an archive file entry. */
+MINIZ_EXPORT mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat);
+
+/* MZ_TRUE if the file is in zip64 format. */
+/* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */
+MINIZ_EXPORT mz_bool mz_zip_is_zip64(mz_zip_archive *pZip);
+
+/* Returns the total central directory size in bytes. */
+/* The current max supported size is <= MZ_UINT32_MAX. */
+MINIZ_EXPORT size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip);
+
+/* Extracts a archive file to a memory buffer using no memory allocation. */
+/* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */
+MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);
+MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);
+
+/* Extracts a archive file to a memory buffer. */
+MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags);
+MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags);
+
+/* Extracts a archive file to a dynamically allocated heap buffer. */
+/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */
+/* Returns NULL and sets the last error on failure. */
+MINIZ_EXPORT void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags);
+MINIZ_EXPORT void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags);
+
+/* Extracts a archive file using a callback function to output the file's data. */
+MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);
+MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);
+
+/* Extract a file iteratively */
+MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);
+MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags);
+MINIZ_EXPORT size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size);
+MINIZ_EXPORT mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState);
+
+#ifndef MINIZ_NO_STDIO
+/* Extracts a archive file to a disk file and sets its last accessed and modified times. */
+/* This function only extracts files, not archive directory records. */
+MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags);
+MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags);
+
+/* Extracts a archive file starting at the current position in the destination FILE stream. */
+MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags);
+MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags);
+#endif
+
+#if 0
+/* TODO */
+ typedef void *mz_zip_streaming_extract_state_ptr;
+ mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);
+ uint64_t mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);
+ uint64_t mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);
+ mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, uint64_t new_ofs);
+ size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size);
+ mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);
+#endif
+
+/* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */
+/* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */
+MINIZ_EXPORT mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);
+
+/* Validates an entire archive by calling mz_zip_validate_file() on each file. */
+MINIZ_EXPORT mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags);
+
+/* Misc utils/helpers, valid for ZIP reading or writing */
+MINIZ_EXPORT mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr);
+MINIZ_EXPORT mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr);
+
+/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */
+MINIZ_EXPORT mz_bool mz_zip_end(mz_zip_archive *pZip);
+
+/* -------- ZIP writing */
+
+#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
+
+/* Inits a ZIP archive writer. */
+/*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/
+/*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/
+MINIZ_EXPORT mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size);
+MINIZ_EXPORT mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags);
+
+MINIZ_EXPORT mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size);
+MINIZ_EXPORT mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags);
+
+#ifndef MINIZ_NO_STDIO
+MINIZ_EXPORT mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning);
+MINIZ_EXPORT mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags);
+MINIZ_EXPORT mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags);
+#endif
+
+/* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */
+/* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */
+/* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */
+/* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */
+/* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */
+/* the archive is finalized the file's central directory will be hosed. */
+MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename);
+MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags);
+
+/* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */
+/* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */
+/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */
+MINIZ_EXPORT mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags);
+
+/* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */
+/* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */
+MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
+ mz_uint64 uncomp_size, mz_uint32 uncomp_crc32);
+
+MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,
+ mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len,
+ const char *user_extra_data_central, mz_uint user_extra_data_central_len);
+
+/* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */
+/* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/
+MINIZ_EXPORT mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size,
+ const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len,
+ const char *user_extra_data_central, mz_uint user_extra_data_central_len);
+
+
+#ifndef MINIZ_NO_STDIO
+/* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */
+/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */
+MINIZ_EXPORT mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);
+
+/* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */
+MINIZ_EXPORT mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size,
+ const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len,
+ const char *user_extra_data_central, mz_uint user_extra_data_central_len);
+#endif
+
+/* Adds a file to an archive by fully cloning the data from another archive. */
+/* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */
+MINIZ_EXPORT mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index);
+
+/* Finalizes the archive by writing the central directory records followed by the end of central directory record. */
+/* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */
+/* An archive must be manually finalized by calling this function for it to be valid. */
+MINIZ_EXPORT mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip);
+
+/* Finalizes a heap archive, returning a poiner to the heap block and its size. */
+/* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */
+MINIZ_EXPORT mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize);
+
+/* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */
+/* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */
+MINIZ_EXPORT mz_bool mz_zip_writer_end(mz_zip_archive *pZip);
+
+/* -------- Misc. high-level helper functions: */
+
+/* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */
+/* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */
+/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */
+/* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */
+MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);
+MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr);
+
+/* Reads a single file from an archive into a heap block. */
+/* If pComment is not NULL, only the file with the specified comment will be extracted. */
+/* Returns NULL on failure. */
+MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags);
+MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr);
+
+#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MINIZ_NO_ARCHIVE_APIS */
diff --git a/lualib/Cache/init.lua b/lualib/Cache/init.lua
index a1194f51..fb3b5a73 100644
--- a/lualib/Cache/init.lua
+++ b/lualib/Cache/init.lua
@@ -1,180 +1 @@
-local log = require "log"
-local Co = require "internal.Co"
-local timer = require "internal.Timer"
-local redis = require "protocol.redis"
-
-local co_self = Co.self
-local co_wait = Co.wait
-local co_wakeup = Co.wakeup
-
-local type = type
-local ipairs = ipairs
-local setmetatable = setmetatable
-
-local table = table
-local unpack = table.unpack
-local remove = table.remove
-local upper = string.upper
-local lower = string.lower
-local splite = string.gmatch
-
-
--- 默认情况下, 保持50个redis连接
-local MAX, COUNT = 50, 0
-
--- 连接池
-local POOL = {}
-
--- 是否已经初始化
-local INITIALIZATION = false
-
--- session创建函数
-local CREATE_CACHE
-
--- 注册命令
-local commands = {
- 'sismember', 'exists'
-}
-
-local function in_command(cmd)
- for _, command in ipairs(commands) do
- if lower(cmd) == command then
- return true
- end
- end
- return false
-end
-
-local wlist = {}
-
-local function add_wait(co)
- wlist[#wlist+1] = co
-end
-
-local function pop_wait()
- return remove(wlist)
-end
-
-local function add_cache(session)
- POOL[#POOL+1] = session
-end
-
-local function pop_cache()
- if #POOL > 0 then
- return remove(POOL)
- end
- if COUNT < MAX then
- COUNT = COUNT + 1
- return CREATE_CACHE()
- end
- add_wait(co_self())
- return co_wait()
-end
-
-
-local Cache = setmetatable({}, {__index = function (_, key)
- if not INITIALIZATION then
- return nil, 'Cache尚未初始化'
- end
- if lower(key) == "publish" or lower(key) == "subscribe" or lower(key) == "psubscribe" then
- return nil, 'Cache error: Cache不支持在缓存中直接使用此命令.'
- end
- local cache = pop_cache()
- if in_command(key) then
- return function (_, ...)
- local ok, ret
- while 1 do
- ok, ret = cache[key](cache, ...)
- if ret ~= 'server close!!' then
- break
- end
- cache:close()
- cache = CREATE_CACHE()
- end
- if #wlist > 0 then
- co_wakeup(pop_wait(), cache)
- else
- add_cache(cache)
- end
- return ok, ret
- end
- end
- return function (_, ...)
- local ok, ret
- local keys = {}
- for k in splite(key, "([^_]+)") do
- keys[#keys+1] = k
- end
- while 1 do
- if #keys > 1 then
- ok, ret = cache:cmd(upper(keys[1]), upper(keys[2]), ...)
- else
- ok, ret = cache:cmd(upper(keys[1]), ...)
- end
- if ret ~= 'server close!!' then
- break
- end
- cache:close()
- cache = CREATE_CACHE()
- end
- if #wlist > 0 then
- co_wakeup(pop_wait(), cache)
- else
- add_cache(cache)
- end
- return ok, ret
- end
-end})
-
--- 初始化
-function Cache.init(opt)
- if INITIALIZATION then
- return nil, "Cache已经初始化."
- end
-
- assert(type(opt) == 'table', "Cache error: 错误的Cache配置文件.")
-
- assert(type(opt.host) == 'string' and opt.host ~= '', "Cache error: 异常的主机名.")
-
- assert(type(opt.port) == 'number' and opt.port > 0 and opt.port <= 65535, "Cache error: 异常的端口.")
-
- assert(not opt.auth or type(opt.auth) == 'string' , "Cache error: 异常的auth.")
-
- assert(not opt.db or type(opt.db) == 'number' and opt.db >= 0 and opt.db <= 15, "Cache error: 异常的db.")
-
- if type(opt.max) == 'number' and opt.max > 0 then
- MAX = opt.max
- end
-
- CREATE_CACHE = function ()
- local times = 1
- local rds
- while 1 do
- rds = redis:new(opt)
- local ok, err = rds:connect()
- if ok then
- break
- end
- log.error('第'..tostring(times)..'次连接失败:'..err.." 3 秒后尝试再次连接")
- times = times + 1
- rds:close()
- timer.sleep(3)
- end
- local ok, ret = rds:cmd("CONFIG", "GET", "TIMEOUT")
- if not INITIALIZATION and ret[2] ~= '0' then
- rds:cmd("CONFIG SET", "TIMEOUT", "0")
- end
- return rds
- end
- add_cache(CREATE_CACHE())
- INITIALIZATION = true
- COUNT = COUNT + 1
- return true
-end
-
--- 连接池数量
-function Cache.count()
- return #POOL
-end
-
-return Cache
\ No newline at end of file
+return require "Cache.redis"
\ No newline at end of file
diff --git a/lualib/Cache/redis.lua b/lualib/Cache/redis.lua
new file mode 100644
index 00000000..2ec67630
--- /dev/null
+++ b/lualib/Cache/redis.lua
@@ -0,0 +1,190 @@
+local class = require "class"
+local log = require "logging"
+local Co = require "internal.Co"
+local timer = require "internal.Timer"
+local redis = require "protocol.redis"
+
+local co_self = Co.self
+local co_wait = Co.wait
+local co_wakeup = Co.wakeup
+
+local ipairs = ipairs
+local assert = assert
+local tostring = tostring
+local setmetatable = setmetatable
+
+local table = table
+local insert = table.insert
+local remove = table.remove
+local upper = string.upper
+local lower = string.lower
+local splite = string.gmatch
+
+local Log = log:new({ dump = true, path = 'Cache'})
+
+-- 注册命令
+local commands = {
+ 'sismember', 'exists', 'pipeline'
+}
+
+local function in_command(cmd)
+ for _, command in ipairs(commands) do
+ if lower(cmd) == command then
+ return true
+ end
+ end
+ return false
+end
+
+local keys = {}
+
+-- 注入函数
+local function in_keys(key)
+ return keys[key]
+end
+
+-- 创建Cache函数
+local function CREATE_CACHE(opt)
+ local rds
+ while 1 do
+ rds = redis:new(opt):set_timeout(3)
+ local ok, err = rds:connect()
+ if ok then
+ if not opt.INITIALIZATION then
+ local opok, ret = assert(rds:cmd("CONFIG", "GET", "TIMEOUT"))
+ if opok and ret[2] ~= '0' then
+ assert(rds:cmd("CONFIG SET", "TIMEOUT", "0"), "SET TIMEOUT faild.")
+ end
+ end
+ break
+ end
+ Log:WARN("[Cache ERROR]: The connection failed. The reasons are: [" .. err .. "], Try to reconnect after 3 seconds")
+ rds:close()
+ timer.sleep(3)
+ end
+ return rds:set_timeout(0)
+end
+
+-- 加入到协程池内
+local function add_wait(self, co)
+ insert(self.co_pool, co)
+end
+
+-- 弹出一个等待协程
+local function pop_wait(self)
+ return remove(self.co_pool)
+end
+
+-- 加入到连接池内
+local function add_cache(self, cache)
+ insert(self.cache_pool, cache)
+end
+
+-- 从连接池内取出一个cache对象
+local function pop_cache(self)
+ if #self.cache_pool > 0 then
+ return remove(self.cache_pool)
+ end
+ if self.current < self.max then
+ self.current = self.current + 1
+ return CREATE_CACHE(self)
+ end
+ add_wait(self, co_self())
+ return co_wait()
+end
+
+-- 构建Cache对象
+local function setmeta(self)
+ keys['count'] = self.count
+ return setmetatable(self, {
+ __index = function(t, key)
+ local f = in_keys(key)
+ if f then
+ return f
+ end
+ if lower(key) == "publish" or lower(key) == "subscribe" or lower(key) == "psubscribe" then
+ return nil, '[Cache ERROR]: Cache不支持在缓存中直接使用此命令.'
+ end
+ if in_command(key) then
+ return function (_, ...)
+ local ok, ret
+ local session
+ while 1 do
+ session = pop_cache(t)
+ ok, ret = session[key](session, ...)
+ if session:isconnected() then
+ break
+ end
+ session:close()
+ session = nil
+ end
+ local co = pop_wait(t)
+ if co then
+ co_wakeup(co, session)
+ return ok, ret
+ end
+ add_cache(t, session)
+ return ok, ret
+ end
+ end
+ return function (_, ...)
+ local ok, ret
+ local keys = {}
+ for k in splite(key, "([^_]+)") do
+ keys[#keys+1] = k
+ end
+ local session
+ while 1 do
+ session = pop_cache(t)
+ if #keys > 1 then
+ ok, ret = session:cmd(upper(keys[1]), upper(keys[2]), ...)
+ else
+ ok, ret = session:cmd(upper(keys[1]), ...)
+ end
+ if session:isconnected() then
+ break
+ end
+ session:close()
+ session = nil
+ end
+ local co = pop_wait(t)
+ if co then
+ co_wakeup(co, session)
+ return ok, ret
+ end
+ add_cache(t, session)
+ return ok, ret
+ end
+ end}) == self
+end
+
+local Cache = class("Cache")
+
+function Cache:ctor (opt)
+ self.host = opt.host
+ self.port = opt.port
+ self.unixdomain = opt.unixdomain
+ self.db = opt.db
+ self.auth = opt.auth
+ self.max = opt.max or 50
+ self.current = 0
+ -- 连接池
+ self.cache_pool = {}
+ -- 协程池
+ self.co_pool = {}
+end
+
+function Cache:connect ()
+ if not self.INITIALIZATION then
+ add_cache(self, pop_cache(self))
+ self.INITIALIZATION = true
+ return setmeta(self)
+ end
+ return true
+end
+
+function Cache:count()
+ return self.current, self.max
+end
+
+return Cache
\ No newline at end of file
diff --git a/lualib/Cache/ssdb.lua b/lualib/Cache/ssdb.lua
new file mode 100644
index 00000000..24ffcbbf
--- /dev/null
+++ b/lualib/Cache/ssdb.lua
@@ -0,0 +1,190 @@
+local class = require "class"
+local log = require "logging"
+local Co = require "internal.Co"
+local timer = require "internal.Timer"
+local ssdb = require "protocol.redis"
+
+local co_self = Co.self
+local co_wait = Co.wait
+local co_wakeup = Co.wakeup
+
+local ipairs = ipairs
+local setmetatable = setmetatable
+
+local table = table
+local insert = table.insert
+local remove = table.remove
+local upper = string.upper
+local lower = string.lower
+local splite = string.gmatch
+
+local Log = log:new({ dump = true, path = 'Cache'})
+
+-- 注册命令
+local commands = { 'exists', 'pipeline' }
+
+local function in_command(cmd)
+ for _, command in ipairs(commands) do
+ if lower(cmd) == command then
+ return true
+ end
+ end
+ return false
+end
+
+local keys = {}
+
+-- 注入函数
+local function in_keys(key)
+ return keys[key]
+end
+
+-- 创建Cache函数
+local function CREATE_CACHE(opt)
+ local rds
+ opt.db = nil
+ while 1 do
+ rds = ssdb:new(opt):set_timeout(3)
+ local ok, err = rds:connect()
+ if ok then
+ -- SSDB 不支持配置命令
+ break
+ end
+ Log:WARN("[Cache ERROR]: The connection failed. The reasons are: [" .. err .. "], Try to reconnect after 3 seconds")
+ rds:close()
+ timer.sleep(3)
+ end
+ return rds:set_timeout(0)
+end
+
+-- 加入到协程池内
+local function add_wait(self, co)
+ insert(self.co_pool, co)
+end
+
+-- 弹出一个等待协程
+local function pop_wait(self)
+ return remove(self.co_pool)
+end
+
+-- 加入到连接池内
+local function add_cache(self, cache)
+ insert(self.cache_pool, cache)
+end
+
+-- 从连接池内取出一个cache对象
+local function pop_cache(self)
+ if #self.cache_pool > 0 then
+ return remove(self.cache_pool)
+ end
+ if self.current < self.max then
+ self.current = self.current + 1
+ return CREATE_CACHE(self)
+ end
+ add_wait(self, co_self())
+ return co_wait()
+end
+
+-- 构建Cache对象
+local function setmeta(self)
+ keys['count'] = self.count
+ return setmetatable(self, {
+ __index = function(t, key)
+ local f = in_keys(key)
+ if f then
+ return f
+ end
+ if lower(key) == "publish" or lower(key) == "subscribe" or lower(key) == "psubscribe" then
+ return nil, '[Cache ERROR]: Cache不支持在缓存中直接使用此命令.'
+ end
+ if in_command(key) then
+ return function (_, ...)
+ local ok, ret
+ local session
+ while 1 do
+ session = pop_cache(t)
+ ok, ret = session[key](session, ...)
+ if session:isconnected() then
+ break
+ end
+ session:close()
+ session = nil
+ end
+ local co = pop_wait(t)
+ if co then
+ co_wakeup(co, session)
+ return ok, ret
+ end
+ add_cache(t, session)
+ return ok, ret
+ end
+ end
+ return function (_, ...)
+ local ok, ret
+ local keys = {}
+ for k in splite(key, "([^_]+)") do
+ keys[#keys+1] = k
+ end
+ if
+ key == 'multi_set' or key == 'multi_get' or key == 'multi_del' or
+ key == 'multi_hget' or key == 'multi_hset' or key == 'multi_hdel' or
+ key == 'multi_zget' or key == 'multi_zset' or key == 'multi_zdel' or
+ key == 'zpop_front' or key == 'zpop_back' or key == 'zpush_front' or key == 'zpush_back'
+ then
+ keys = {key}
+ end
+ local session
+ while 1 do
+ session = pop_cache(t)
+ if #keys > 1 then
+ ok, ret = session:cmd(upper(keys[1]), upper(keys[2]), ...)
+ else
+ ok, ret = session:cmd(upper(keys[1]), ...)
+ end
+ if session:isconnected() then
+ break
+ end
+ session:close()
+ session = nil
+ end
+ local co = pop_wait(t)
+ if co then
+ co_wakeup(co, session)
+ return ok, ret
+ end
+ add_cache(t, session)
+ return ok, ret
+ end
+ end}) == self
+end
+
+local Cache = class("Cache")
+
+function Cache:ctor (opt)
+ self.host = opt.host
+ self.port = opt.port
+ self.unixdomain = opt.unixdomain
+ self.db = opt.db
+ self.auth = opt.auth
+ self.max = opt.max or 50
+ self.current = 0
+ -- 连接池
+ self.cache_pool = {}
+ -- 协程池
+ self.co_pool = {}
+end
+
+function Cache:connect ()
+ if not self.INITIALIZATION then
+ add_cache(self, pop_cache(self))
+ self.INITIALIZATION = true
+ return setmeta(self)
+ end
+ return true
+end
+
+function Cache:count()
+ return self.current, self.max
+end
+
+return Cache
\ No newline at end of file
diff --git a/lualib/DB/init.lua b/lualib/DB/init.lua
index e5a5184f..36b57870 100644
--- a/lualib/DB/init.lua
+++ b/lualib/DB/init.lua
@@ -1,511 +1 @@
-local mysql = require "protocol.mysql"
-local timer = require "internal.Timer"
-local co = require "internal.Co"
-local log = require "log"
-
-local co_self = co.self
-local co_wait = co.wait
-local co_wakeup = co.wakeup
-
-local type = type
-local tostring = tostring
-local tonumber = tonumber
-local assert = assert
-local ipairs = ipairs
-
-local rep = string.rep
-local find = string.find
-local fmt = string.format
-local lower = string.lower
-local upper = string.upper
-local match = string.match
-
-local insert = table.insert
-local remove = table.remove
-local concat = table.concat
-local unpack = table.unpack
-
-local os_time = os.time
-
-local SELECT = "SELECT"
-
-local INSERT = "INSERT INTO"
-
-local UPDATE = "UPDATE"
-
-local DELETE = "DELETE"
-
-local SET = "SET"
-
-local FROM = "FROM"
-
-local WHERE = "WHERE"
-
-local IN = "IN"
-
-local IS = "IS"
-
-local NOT = "NOT"
-
-local BETWEEN = "BETWEEN"
-
-local AND = " AND "
-
-local LIMIT = "LIMIT"
-
-local DESC = "DESC"
-
-local ASC = "ASC"
-
-local ORDERBY = "ORDER BY"
-
-local GROUPBY = "GROUP BY"
-
-local COMMA = ", "
-
-local INITIALIZATION
-
--- 最大DB连接数量
-local MAX, COUNT = 50, 0
-
--- 空闲连接时间
-local WAIT_TIMEOUT = 2592000
-
--- 数据库连接创建函数
-local DB_CREATE
-
--- 等待db对象的协程列表
-local wlist = {}
-
-local function add_wait(co)
- wlist[#wlist+1] = co
-end
-
-local function pop_wait()
- return remove(wlist)
-end
-
--- 数据库连接池
-local POOL = {}
-
-local function add_db(db)
- POOL[#POOL + 1] = { session = db, ttl = os_time() }
-end
-
--- 负责创建连接/加入等待队列
-local function get_db()
- if #POOL > 0 then
- while 1 do
- local db = remove(POOL)
- if not db then break end -- 连接池内已经没有连接了
- if db.ttl > os_time() - WAIT_TIMEOUT then
- return db.session
- end
- db.session:close()
- COUNT = COUNT - 1
- end
- end
- if COUNT < MAX then
- COUNT = COUNT + 1
- local db = DB_CREATE()
- if db then
- return db
- end
- COUNT = COUNT - 1
- -- 连接失败或者其他情况, 将等待其他协程唤醒; 保证公平竞争数据库连接
- end
- add_wait(co_self())
- return co_wait()
-end
-
-
--- 格式化处理函数
--- 将['field', '=', 'a'] 格式化为 field = a
--- 将['field', 'NOT', 'IN', '(1, 2, 3)'] 格式化为 field NOT IN (1, 2, 3)
-local function format(t)
- return fmt(concat({rep("%s ", #t-1), "%s"}), unpack(t))
-end
-
-local function format_value1(t)
- return fmt("%s %s '%s'", unpack(t))
-end
-
-local function format_value2(t, split)
- local tmp = {}
- for i=1, #t do
- tmp[i] = "'%s'"
- end
- return fmt(concat(tmp, split), unpack(t))
-end
-
-local function format_value3(t)
- return fmt("%s %s '%s'", unpack(t))
-end
-
-local function format_value4(t)
- return fmt("%s %s %s '%s'", unpack(t))
-end
-
--- 执行
-local function execute(query)
- if query.SELECT then
- assert(query.FROM and query.WHERE, "查询语句必须使用from方法与where条件.")
- end
- if query.DELETE then
- assert(query.WHERE, "删除语句请加上where条件")
- end
- if query.INSERT then
- assert(query.FILEDS and query.VALUES, "插入语句请加上fields与values")
- end
- if query.UPDATE then
- assert(query.WHERE, "更新语句请加上where条件")
- end
- local QUERY = concat(query, " ")
- -- print(QUERY)
- local db, ret, err
- while 1 do
- db = get_db()
- if db then
- ret, err = db:query(QUERY)
- if db.state then
- break
- end
- log.error(err)
- db:close()
- db, ret, err = nil
- end
- end
- if #wlist > 0 then
- co_wakeup(pop_wait(), db)
- else
- add_db(db)
- end
- return ret, err
-end
-
-local function limit(query, limit1, limit2)
- local t1 = type(limit1)
- local t2 = type(limit2)
- assert(query and type(query) == 'table' and (t1 == "number" or t1 == "string" ), "错误的限制条件(limit):"..tostring(limit1))
-
- insert(query, LIMIT)
-
- local limits = {tostring(limit1)}
- if t2 == "number" or tpy == "string" then
- insert(limits, tostring(limit2))
- end
-
- insert(query, concat(limits, COMMA))
-
- return query
-end
-
--- 降序
-local function desc(query)
- insert(query, DESC)
- return query
-end
--- 升序
-local function asc(query)
- insert(query, ASC)
- return query
-end
-
--- 聚合字段
-local function groupby(query, fields)
- local tpy = type(fields)
- assert(query and (tpy == "string" or tpy == "table"), "错误的聚合字段类型(fields):"..tostring(fields))
- insert(query, GROUPBY)
- if tpy == "string" then
- insert(query, fields)
- end
- if tpy == "table" then
- insert(query, concat(fields, COMMA))
- end
- return query
-end
-
--- 排序字段
-local function orderby(query, orders)
- local tpy = type(orders)
- assert(query and (tpy == "string" or tpy == "table"), "错误的排序条件(orders):"..tostring(orders))
- insert(query, ORDERBY)
- if tpy == "string" then
- insert(query, orders)
- end
- if tpy == "table" then
- insert(query, concat(orders, COMMA))
- end
- return query
-end
-
--- 条件
-local function where(query, conditions)
- local tpy = type(conditions)
- assert(query and (tpy == "string" or tpy == "table"), "错误的条件类型(where):"..tostring(conditions))
- insert(query, WHERE)
- if tpy == "string" then
- insert(query, conditions)
- end
- if tpy == "table" then
- local CONDITIONS = {}
- for index, condition in ipairs(conditions) do
- if type(condition) == "table" and #condition == 3 then
- local con2 = upper(condition[2])
- if con2 == IN or con2 == BETWEEN then -- 假设比较符是IN 或者 BETWEEN
- local LEFT, RIGHT = '', ''
- local c = AND
- if con2 == IN then
- c = COMMA
- LEFT, RIGHT = '(', ')'
- end
- insert(CONDITIONS, format({condition[1], con2, LEFT..format_value2(condition[3], c)..RIGHT}))
- elseif con2 == IS then
- insert(CONDITIONS, concat(condition, " "))
- elseif find(condition[3], condition[1]) then
- insert(CONDITIONS, concat(condition, " "))
- else
- insert(CONDITIONS, format_value3(condition))
- end
- elseif type(condition) == "table" and #condition == 4 then
- local con2 = upper(condition[2])
- local con3 = upper(condition[3])
- if con3 == IN or con3 == BETWEEN then -- 假设比较符是IN 或者 BETWEEN
- local LEFT, RIGHT = '', ''
- local c = AND
- if con3 == IN then
- c = COMMA
- LEFT, RIGHT = '(', ')'
- end
- insert(CONDITIONS, format({condition[1], con2, con3, LEFT..format_value2(condition[4], c)..RIGHT}))
- elseif con2 == IS then
- insert(CONDITIONS, concat(condition, " "))
- else
- insert(CONDITIONS, format_value4(condition))
- end
- else -- 假设condition为 "AND" 、 "OR"
- insert(CONDITIONS, upper(condition))
- end
- end
- insert(query, concat(CONDITIONS, " "))
- end
- query.WHERE = true
- return query
-end
-
--- 表(s)
-local function from(query, tables)
- local tpy = type(tables)
- assert(query and (tpy == "string" or tpy == "table"), "错误的表名:"..tostring(tables))
- insert(query, FROM)
- if tpy == "string" then
- insert(query, tables)
- end
- if tpy == "table" then
- insert(query, concat(tables, COMMA))
- end
- query.FROM = true
- return query
-end
-
--- 插入语句专用函数 --
-local function values(query, values)
- local tpy = type(values)
- assert(tpy == "table" and #values > 0 and type(values[1]) == "table" , "错误的值类型(values):"..tostring(values))
- local VALUES = {}
- for _, value in ipairs(values) do
- insert(VALUES, "("..format_value2(value, COMMA)..")")
- end
- insert(query, "VALUES")
- insert(query, concat(VALUES, COMMA))
- query.VALUES = true
- return query
-end
-
-local function fields(query, fields)
- local tpy = type(fields)
- assert(tpy == "table" and #fields > 0, "错误的字段类型(values):"..tostring(fields))
- insert(query, "("..concat(fields, COMMA)..")")
- query.FILEDS = true
- return query
-end
--- 插入语句专用函数 --
-
-
-
-
-
--- 更新语句专用 --
-local function set(query, values)
- local tpy = type(values)
- assert(tpy == "table" and #values > 0 and type(values[1]) == "table" , "错误的值类型(values):"..tostring(values))
- local VALUES = {}
- for _, value in ipairs(values) do
- insert(VALUES, format_value1(value))
- end
- insert(query, SET)
- insert(query, concat(VALUES, COMMA))
- return query
-end
--- 更新语句专用 --
-
-
-
-local DB = {}
-
--- 初始化数据库
-function DB.init(opt)
- if INITIALIZATION then
- return nil, "DB已经初始化."
- end
- if type(opt) ~= 'table' then
- return nil, '错误的DB配置文件.'
- end
- if type(opt.max) == 'number' then
- MAX = opt.max
- end
- local config = {
- host = opt.host or 'localhost',
- port = opt.port or 3306,
- database = opt.database,
- user = opt.user,
- password = opt.password,
- }
- DB_CREATE = function(...)
- local times = 1
- local db
- while 1 do
- db = mysql:new()
- local connect, err = db:connect(config)
- if connect then
- break
- end
- log.error('第'..tostring(times)..'次连接失败:'..err.." 3 秒后尝试再次连接")
- db:close()
- times = times + 1
- timer.sleep(3)
- end
- db:query(fmt('SET wait_timeout=%s', tostring(WAIT_TIMEOUT)))
- db:query(fmt('SET interactive_timeout=%s', tostring(WAIT_TIMEOUT)))
- return db
- end
- add_db(get_db())
- INITIALIZATION = true
- return true
-end
-
-
--- 查询语句
-function DB.select(fields)
- if not INITIALIZATION then
- return nil, "DB尚未初始化"
- end
- local tpy = type(fields)
- assert(tpy == "string" or tpy == "table", "错误的字段类型(fields):"..tostring(fields))
- local query = {
- [1] = SELECT,
- SELECT = true,
- from = from,
- where = where,
- orderby = orderby,
- groupby = groupby,
- desc = desc,
- asc = asc,
- limit = limit,
- execute = execute,
- }
- if tpy == "string" then
- insert(query, fields)
- end
- if tpy == "table" then
- insert(query, concat(fields, COMMA))
- end
- return query
-end
-
--- 插入语句
-function DB.insert(table_name)
- if not INITIALIZATION then
- return nil, "DB尚未初始化"
- end
- local tpy = type(table_name)
- assert(tpy == "string", "错误的表名(table_name):"..tostring(table_name))
- return {
- [1] = INSERT,
- [2] = table_name,
- INSERT = true,
- fields = fields,
- values = values,
- execute = execute,
- }
-end
-
--- 更新语句
-function DB.update(table_name)
- if not INITIALIZATION then
- return nil, "DB尚未初始化"
- end
- local tpy = type(table_name)
- assert(tpy == "string" or tpy == "tables", "错误的表名(table_name):"..tostring(table_name))
- return {
- [1] = UPDATE,
- [2] = table_name,
- UPDATE = true,
- set = set,
- where = where,
- limit = limit,
- execute = execute,
- }
-end
-
--- 删除语句
-function DB.delete(table_name)
- if not INITIALIZATION then
- return nil, "DB尚未初始化"
- end
- local tpy = type(table_name)
- assert(tpy == "string" or tpy == "tables", "错误的表名(table_name):"..tostring(table_name))
- return {
- [1] = DELETE,
- [2] = FROM,
- [3] = table_name,
- DELETE = true,
- where = where,
- limit = limit,
- orderby = orderby,
- execute = execute,
- }
-end
-
--- 原始SQL
-function DB.query(query)
- if not INITIALIZATION then
- return nil, "DB尚未初始化"
- end
- assert(type(query) == 'string' and query ~= '' , "原始SQL类型错误(query):"..tostring(query))
- local db, ret, err
- while 1 do
- db = get_db()
- if db then
- ret, err = db:query(query)
- if db.state then
- break
- end
- log.error(err)
- db:close()
- db, ret, err = nil
- end
- end
- if #wlist > 0 then
- co_wakeup(pop_wait(), db)
- else
- add_db(db)
- end
- return ret, err
-end
-
-function DB.count( ... )
- return #POOL
-end
-
-return DB
\ No newline at end of file
+return require "DB.mysql"
diff --git a/lualib/DB/mssql.lua b/lualib/DB/mssql.lua
new file mode 100644
index 00000000..3f9795f6
--- /dev/null
+++ b/lualib/DB/mssql.lua
@@ -0,0 +1,230 @@
+local class = require "class"
+
+local timer = require "internal.Timer"
+local mssql = require "protocol.mssql"
+
+local log = require "logging"
+local Log = log:new({ dump = true, path = 'DB'})
+
+local crypt = require "crypt"
+local hashkey = crypt.hashkey
+
+local co = require "internal.Co"
+local co_self = co.self
+local co_wait = co.wait
+local co_spawn = co.spawn
+local co_wakeup = co.wakeup
+
+local type = type
+local error = error
+local xpcall = xpcall
+local assert = assert
+
+local fmt = string.format
+
+local insert = table.insert
+local remove = table.remove
+
+-- 数据库连接创建函数
+local function DB_CREATE (opt)
+ local db
+ while 1 do
+ db = mssql:new(opt)
+ db:set_timeout(3)
+ local connect, err = db:connect()
+ if connect then
+ db:set_timeout(0)
+ break
+ end
+ Log:WARN("The connection failed. The reasons are: [" .. err .. "], Try to reconnect after 3 seconds")
+ timer.sleep(3)
+ db:close()
+ end
+ return db
+end
+
+local function add_wait(self, co)
+ insert(self.co_pool, co)
+end
+
+local function pop_wait(self)
+ return remove(self.co_pool)
+end
+
+local function add_db(self, db)
+ insert(self.db_pool, db)
+end
+
+-- 负责创建连接/加入等待队列
+local function pop_db(self)
+ local session = remove(self.db_pool)
+ if session then
+ return session
+ end
+ if self.current < self.max then
+ self.current = self.current + 1
+ return DB_CREATE(self)
+ end
+ add_wait(self, co_self())
+ return co_wait()
+end
+
+local function run_query(self, query)
+ local db, ret, err
+ while 1 do
+ db = pop_db(self)
+ if db then
+ ret, err = db:query(query)
+ if db.state then
+ break
+ end
+ db:close()
+ self.current = self.current - 1
+ db, ret, err = nil, nil, nil
+ end
+ end
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ return ret, err
+end
+
+local DB = class("DB")
+
+function DB:ctor(opt)
+ self.host = opt.host
+ self.port = opt.port
+ self.unixdomain = opt.unixdomain
+ self.username = opt.username
+ self.password = opt.password
+ self.database = opt.database
+ self.TSQL = opt.TSQL
+ self.max = opt.max or 50
+ self.current = 0
+ -- 协程池
+ self.co_pool = {}
+ -- 连接池
+ self.db_pool = {}
+end
+
+function DB:connect ()
+ if not self.INITIALIZATION then
+ add_db(self, pop_db(self))
+ self.INITIALIZATION = true
+ return self.INITIALIZATION
+ end
+ return self.INITIALIZATION
+end
+
+local function traceback(msg)
+ return fmt("[%s] %s", os.date("%Y/%m/%d %H:%M:%S"), debug.traceback(co_self(), msg, 3))
+end
+
+function DB:transaction(f)
+ assert(self.INITIALIZATION, "DB needs to be initialized first.")
+ assert(type(f) == 'function', "A function must be passed to describe the execution of the transaction.")
+ local db, ret, err
+ while 1 do
+ db = pop_db(self)
+ if db then
+ ret, err = db:query("BEGIN TRAN;")
+ if db.state then
+ break
+ end
+ db:close()
+ self.current = self.current - 1
+ end
+ -- db, ret, err = nil, nil, nil
+ end
+ -- 每个事务都有独立的session
+ local session = { nil, nil, nil }
+ session.query = function (self, sql)
+ assert(self and self == session, "Must use the syntax of `session:query()`")
+ if self.over then
+ return nil, "Please use `return session:rollback()` or `return session:commit()` after the transaction process is over or Process error."
+ end
+ assert(db.state, "MSSQL transaction session closed. 1")
+ local ret, err = db:query(sql)
+ assert(db.state, "MSSQL transaction session closed. 2")
+ return ret, err
+ end
+ session.rollback = function ( self )
+ assert(self and self == session, "Must use the syntax of `session:rollback()`")
+ assert(db.state, "MSSQL transaction session closed. 3")
+ db:query("ROLLBACK TRAN;")
+ assert(db.state, "MSSQL transaction session closed. 4")
+ self.over = true
+ return { state = "rollback" }
+ end
+ session.commit = function ( self )
+ assert(self and self == session, "Must use the syntax of `session:commit()`")
+ assert(db.state, "MSSQL transaction session closed. 5")
+ db:query("COMMIT TRAN;")
+ assert(db.state, "MSSQL transaction session closed. 6")
+ self.over = true
+ return { state = "successed" }
+ end
+
+ local ok, info = xpcall(f, traceback, session)
+ if not ok then
+ -- 如果在自定义事务流程的内部发生了错误
+ if not db.state then
+ self.current = self.current - 1
+ db:close()
+ local co = pop_wait(self)
+ if co then
+ co_spawn(function ( ... )
+ co_wakeup(co, pop_db(self))
+ end)
+ end
+ return nil, info
+ end
+ db:query("ROLLBACK TRAN;")
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ return nil, info
+ end
+ -- 如果定义的事务没有以`commit`或者`rollback`结尾.
+ if type(info) ~= 'table' or (info.state ~= "successed" and info.state ~= 'rollback') then
+ db:query("ROLLBACK TRAN;")
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ error("Must return after transaction ends`session:commit()`or`session:rollback()`.")
+ end
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ return info.state == "successed" and true or false
+end
+
+-- 原始查询语句
+function DB:query(query)
+ assert(self.INITIALIZATION, "DB needs to be initialized first.")
+ return run_query(self, assert(type(query) == 'string' and query ~= '' and query , "Invalid MSSQL syntax."))
+end
+
+-- 字符串安全转义
+function DB.quote_to_str( str )
+ return mssql.quote_to_str(str)
+end
+
+function DB:count()
+ assert(self.INITIALIZATION, "DB needs to be initialized first.")
+ return self.current, self.max, #self.co_pool, #self.db_pool
+end
+
+return DB
diff --git a/lualib/DB/mysql.lua b/lualib/DB/mysql.lua
new file mode 100644
index 00000000..e6caff73
--- /dev/null
+++ b/lualib/DB/mysql.lua
@@ -0,0 +1,283 @@
+local class = require "class"
+
+local timer = require "internal.Timer"
+local mysql = require "protocol.mysql"
+
+local log = require "logging"
+local Log = log:new({ dump = true, path = 'DB'})
+
+local co = require "internal.Co"
+local co_self = co.self
+local co_wait = co.wait
+local co_wakeup = co.wakeup
+
+local type = type
+local error = error
+local xpcall = xpcall
+local assert = assert
+
+local fmt = string.format
+
+local insert = table.insert
+local remove = table.remove
+
+-- 空闲连接时间
+local WAIT_TIMEOUT = 31536000
+local INTERACTIVE_TIMEOUT = 31536000
+
+-- 数据库连接创建函数
+local function DB_CREATE (opt)
+ local db
+ while 1 do
+ db = mysql:new(opt)
+ db:set_timeout(3)
+ local connect, err = db:connect()
+ if connect then
+ assert(db:query(fmt('SET wait_timeout=%u', WAIT_TIMEOUT)))
+ assert(db:query(fmt('SET interactive_timeout=%u', INTERACTIVE_TIMEOUT)))
+ db:set_timeout(0)
+ break
+ end
+ Log:WARN("The connection failed. The reasons are: [" .. err .. "], Try to reconnect after 3 seconds")
+ timer.sleep(3)
+ db:close()
+ end
+ return db
+end
+
+local function add_wait(self, co)
+ insert(self.co_pool, co)
+end
+
+local function pop_wait(self)
+ return remove(self.co_pool)
+end
+
+local function add_db(self, db)
+ insert(self.db_pool, db)
+end
+
+-- 负责创建连接/加入等待队列
+local function pop_db(self)
+ local session = remove(self.db_pool)
+ if session then
+ return session
+ end
+ if self.current < self.max then
+ self.current = self.current + 1
+ return DB_CREATE(self)
+ end
+ add_wait(self, co_self())
+ return co_wait()
+end
+
+local function run_query(self, query)
+ local db, ret, err
+ while 1 do
+ db = pop_db(self)
+ if db then
+ ret, err = db:query(query)
+ if db.state then
+ break
+ end
+ db:close()
+ self.current = self.current - 1
+ db, ret, err = nil, nil, nil
+ end
+ end
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ return ret, err
+end
+
+local function run_execute(self, query, ...)
+ local db, ret, err
+ while 1 do
+ db = pop_db(self)
+ if db then
+ ret, err = db:execute(query, ...)
+ if db.state then
+ break
+ end
+ db:close()
+ self.current = self.current - 1
+ db, ret, err = nil, nil, nil
+ end
+ end
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ return ret, err
+end
+
+-- 事务session
+local session = class("session")
+
+function session:ctor(opt)
+ self.db = opt.db
+ self.over = false
+end
+
+function session:execute(sql, ...)
+ if self.over then
+ return nil, "Please use `return session:rollback()` or `return session:commit()` after the transaction process is over or Process error."
+ end
+ -- assert(self.db.state, "MySQL transaction session closed. 1")
+ local ret, err = self.db:execute(sql, ...)
+ assert(self.db.state, "MySQL transaction session closed. 1")
+ return ret, err
+end
+
+function session:query(sql)
+ if self.over then
+ return nil, "Please use `return session:rollback()` or `return session:commit()` after the transaction process is over or Process error."
+ end
+ -- assert(self.db.state, "MySQL transaction session closed. 1")
+ local ret, err = self.db:query(sql)
+ assert(self.db.state, "MySQL transaction session closed. 2")
+ return ret, err
+end
+
+function session:rollback()
+ if self.over then
+ return
+ end
+ assert(self.db.state, "MySQL transaction session closed. 3")
+ self.db:query("rollback; set autocommit=1;")
+ assert(self.db.state, "MySQL transaction session closed. 4")
+ self.over = true
+ self.db = nil
+ return { state = "rollback" }
+end
+
+function session:commit()
+ if self.over then
+ return
+ end
+ assert(self.db.state, "MySQL transaction session closed. 5")
+ self.db:query("set autocommit=1;")
+ assert(self.db.state, "MySQL transaction session closed. 6")
+ self.over = true
+ self.db = nil
+ return { state = "successed" }
+end
+
+local DB = class("DB")
+
+function DB:ctor(opt)
+ self.host = opt.host
+ self.port = opt.port
+ self.unixdomain = opt.unixdomain
+ self.username = opt.username
+ self.password = opt.password
+ self.database = opt.database
+ self.charset = opt.charset or 'utf8'
+ self.max = opt.max or 50
+ self.current = 0
+ -- 协程池
+ self.co_pool = {}
+ -- 连接池
+ self.db_pool = {}
+end
+
+function DB:connect ()
+ if not self.INITIALIZATION then
+ add_db(self, pop_db(self))
+ self.INITIALIZATION = true
+ return self.INITIALIZATION
+ end
+ return self.INITIALIZATION
+end
+
+local function traceback(msg)
+ return fmt("[%s] %s", os.date("%Y/%m/%d %H:%M:%S"), debug.traceback(co_self(), msg, 3))
+end
+
+function DB:transaction(f)
+ assert(self.INITIALIZATION, "DB needs to be initialized first.")
+ assert(type(f) == 'function', "A function must be passed to describe the execution of the transaction.")
+ local db, ret, err
+ while 1 do
+ db = pop_db(self)
+ if db then
+ ret, err = db:query("set autocommit=0;")
+ if db.state then
+ break
+ end
+ db:close()
+ self.current = self.current - 1
+ end
+ -- db, ret, err = nil, nil, nil
+ end
+
+ local ok, info = xpcall(f, traceback, session:new{ db = db })
+ if not ok then
+ -- 如果在自定义事务流程的内部发生了错误
+ if not db.state then
+ self.current = self.current - 1
+ db:close()
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, pop_db(self))
+ end
+ return nil, info
+ end
+ db:query("rollback; set autocommit=1;")
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ return nil, info
+ end
+ -- 如果定义的事务没有以`commit`或者`rollback`结尾.
+ if type(info) ~= 'table' or (info.state ~= "successed" and info.state ~= 'rollback') then
+ db:query("rollback; set autocommit=1;")
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ error("After the transaction ends, either 'return session:commit()' or 'return session:rollback()' must be required.")
+ end
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ return info.state == "successed" and true or false
+end
+
+-- 查询接口
+function DB:query(query)
+ assert(self.INITIALIZATION, "DB needs to be initialized first.")
+ return run_query(self, assert(type(query) == 'string' and query ~= '' and query , "Invalid MySQL query syntax."))
+end
+
+-- 预处理接口
+function DB:execute(query, ...)
+ assert(self.INITIALIZATION, "DB needs to be initialized first.")
+ return run_execute(self, assert(type(query) == 'string' and query ~= '' and query , "Invalid MySQL prepare syntax."), ...)
+end
+
+-- 字符串安全转义
+function DB.quote_to_str( str )
+ return mysql.quote_to_str(str)
+end
+
+function DB:count()
+ assert(self.INITIALIZATION, "DB needs to be initialized first.")
+ return self.current, self.max, #self.co_pool, #self.db_pool
+end
+
+return DB
diff --git a/lualib/DB/pgsql.lua b/lualib/DB/pgsql.lua
new file mode 100644
index 00000000..2f4e1fc6
--- /dev/null
+++ b/lualib/DB/pgsql.lua
@@ -0,0 +1,229 @@
+local class = require "class"
+
+local timer = require "internal.Timer"
+local pgsql = require "protocol.pgsql"
+
+local log = require "logging"
+local Log = log:new({ dump = true, path = 'DB'})
+
+local crypt = require "crypt"
+local hashkey = crypt.hashkey
+
+local co = require "internal.Co"
+local co_self = co.self
+local co_wait = co.wait
+local co_spawn = co.spawn
+local co_wakeup = co.wakeup
+
+local type = type
+local error = error
+local xpcall = xpcall
+local assert = assert
+
+local fmt = string.format
+
+local insert = table.insert
+local remove = table.remove
+
+-- 数据库连接创建函数
+local function DB_CREATE (opt)
+ local db
+ while 1 do
+ db = pgsql:new(opt)
+ db:set_timeout(3)
+ local connect, err = db:connect()
+ if connect then
+ db:set_timeout(0)
+ break
+ end
+ Log:WARN("The connection failed. The reasons are: [" .. err .. "], Try to reconnect after 3 seconds")
+ timer.sleep(3)
+ db:close()
+ end
+ return db
+end
+
+local function add_wait(self, co)
+ insert(self.co_pool, co)
+end
+
+local function pop_wait(self)
+ return remove(self.co_pool)
+end
+
+local function add_db(self, db)
+ insert(self.db_pool, db)
+end
+
+-- 负责创建连接/加入等待队列
+local function pop_db(self)
+ local session = remove(self.db_pool)
+ if session then
+ return session
+ end
+ if self.current < self.max then
+ self.current = self.current + 1
+ return DB_CREATE(self)
+ end
+ add_wait(self, co_self())
+ return co_wait()
+end
+
+local function run_query(self, query)
+ local db, ret, err
+ while 1 do
+ db = pop_db(self)
+ if db then
+ ret, err = db:query(query)
+ if db.state then
+ break
+ end
+ db:close()
+ self.current = self.current - 1
+ db, ret, err = nil, nil, nil
+ end
+ end
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ return ret, err
+end
+
+local DB = class("DB")
+
+function DB:ctor(opt)
+ self.host = opt.host
+ self.port = opt.port
+ self.unixdomain = opt.unixdomain
+ self.username = opt.username
+ self.password = opt.password
+ self.database = opt.database
+ self.charset = opt.charset
+ self.max = opt.max or 50
+ self.current = 0
+ -- 协程池
+ self.co_pool = {}
+ -- 连接池
+ self.db_pool = {}
+end
+
+function DB:connect ()
+ if not self.INITIALIZATION then
+ add_db(self, pop_db(self))
+ self.INITIALIZATION = true
+ return self.INITIALIZATION
+ end
+ return self.INITIALIZATION
+end
+
+local function traceback(msg)
+ return fmt("[%s] %s", os.date("%Y/%m/%d %H:%M:%S"), debug.traceback(co_self(), msg, 3))
+end
+
+function DB:transaction(f)
+ assert(self.INITIALIZATION, "DB needs to be initialized first.")
+ assert(type(f) == 'function', "A function must be passed to describe the execution of the transaction.")
+ local db, ret, err
+ while 1 do
+ db = pop_db(self)
+ if db then
+ ret, err = db:query("BEGIN;")
+ if db.state then
+ break
+ end
+ db:close()
+ self.current = self.current - 1
+ end
+ -- db, ret, err = nil, nil, nil
+ end
+ -- 每个事务都有独立的session
+ local session = { nil, nil, nil }
+ session.query = function (self, sql)
+ assert(self and self == session, "Must use the syntax of `session:query()`")
+ if self.over then
+ return nil, "Please use `return session:rollback()` or `return session:commit()` after the transaction process is over or Process error."
+ end
+ assert(db.state, "PGSQL transaction session closed. 1")
+ local ret, err = db:query(sql)
+ assert(db.state, "PGSQL transaction session closed. 2")
+ return ret, err
+ end
+ session.rollback = function ( self )
+ assert(self and self == session, "Must use the syntax of `session:rollback()`")
+ assert(db.state, "PGSQL transaction session closed. 3")
+ db:query("ROLLBACK;")
+ assert(db.state, "PGSQL transaction session closed. 4")
+ self.over = true
+ return { state = "rollback" }
+ end
+ session.commit = function ( self )
+ assert(self and self == session, "Must use the syntax of `session:commit()`")
+ assert(db.state, "PGSQL transaction session closed. 5")
+ db:query("COMMIT;")
+ assert(db.state, "PGSQL transaction session closed. 6")
+ self.over = true
+ return { state = "successed" }
+ end
+ local ok, info = xpcall(f, traceback, session)
+ if not ok then
+ -- 如果在自定义事务流程的内部发生了错误
+ if not db.state then
+ self.current = self.current - 1
+ db:close()
+ local co = pop_wait(self)
+ if co then
+ co_spawn(function ( ... )
+ co_wakeup(co, pop_db(self))
+ end)
+ end
+ return nil, info
+ end
+ db:query("ROLLBACK;")
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ return nil, info
+ end
+ -- 如果定义的事务没有以`commit`或者`rollback`结尾.
+ if type(info) ~= 'table' or (info.state ~= "successed" and info.state ~= 'rollback') then
+ db:query("ROLLBACK;")
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ error("Must return after transaction ends`session:commit()`or`session:rollback()`.")
+ end
+ local co = pop_wait(self)
+ if co then
+ co_wakeup(co, db)
+ else
+ add_db(self, db)
+ end
+ return info.state == "successed" and true or false
+end
+
+-- 原始查询语句
+function DB:query(query)
+ assert(self.INITIALIZATION, "DB needs to be initialized first.")
+ return run_query(self, assert(type(query) == 'string' and query ~= '' and query , "Invalid PGSQL syntax."))
+end
+
+-- 字符串安全转义
+function DB.quote_to_str( str )
+ return pgsql.quote_to_str(str)
+end
+
+function DB:count()
+ assert(self.INITIALIZATION, "DB needs to be initialized first.")
+ return self.current, self.max, #self.co_pool, #self.db_pool
+end
+
+return DB
diff --git a/lualib/MQ/init.lua b/lualib/MQ/init.lua
deleted file mode 100644
index 29b02942..00000000
--- a/lualib/MQ/init.lua
+++ /dev/null
@@ -1,191 +0,0 @@
-local log = require "log"
-local class = require "class"
-local Timer = require "internal.Timer"
-local mqtt = require "protocol.mqtt"
-local redis = require "protocol.redis"
-
-local type = type
-local math = math
-local random = math.random
-local stirng = string
-local fmt = string.format
-local assert = assert
-
-local mq = class("mq")
-
-function mq:ctor(opt)
- self.id = opt.id -- mqtt需要id可以指定
- self.host = opt.host -- 地址
- self.port = opt.port -- 端口
- self.type = opt.type -- 消息队列种类
- self.auth = opt.auth -- redis需要auth可以指定
- self.clean = opt.clean or true -- mqtt 默认清除会话
- self.username = opt.username -- mqtt只支持用户名+密码认证
- self.password = opt.password -- mqtt只支持用户名+密码认证
-end
-
-local function mq_login(self)
- local times = 1
- while 1 do
- if self.type == 'redis' then
- local rds = redis:new {auth = self.auth, host = self.host, port = self.port}
- local ok, err = rds:connect()
- if ok then
- return rds
- end
- rds:close()
- log.error('连接mq(redis)失败:'..(err or "unknow")..'.正在尝试重连')
- Timer.sleep(3)
- times = times + 1
- elseif self.type == 'mqtt' then
- local mqtt = mqtt:new {
- host = self.host,
- port = self.port,
- auth = {
- username = self.username,
- password = self.password
- },
- id = self.id or fmt('luamqtt-cf-v1-%X', random(1, 0xFFFFFFFF)),
- }
- local ok, err = mqtt:connect()
- if ok then
- return mqtt
- end
- mqtt:close()
- log.error('连接mq(mqtt)失败:'..(err or "unknow")..'.正在尝试重连')
- Timer.sleep(3)
- times = times + 1
- else
- error("未知的mq类型.")
- end
- end
-end
-
-local function redis_subscribe(self)
- local sub_mq, err = mq_login(self)
- if not sub_mq then
- return nil, err
- end
- self.sub_mq = sub_mq
- return sub_mq:subscribe(self.pattern, self.func)
-end
-
-local function mqtt_subscribe(self)
- local sub_mq, err = mq_login(self)
- if not sub_mq then
- return nil, err
- end
- self.sub_mq = sub_mq
- return sub_mq:subscribe({qos = 2, topic = self.pattern, clean = self.clean}, self.func)
-end
-
-local function redis_publish(self)
- local index = 1
- while 1 do
- if not self.pub_mq then
- local pub_mq, err = mq_login(self)
- if not pub_mq then
- return nil, err
- end
- self.pub_mq = pub_mq
- end
- while 1 do
- local msg = self.queue[index]
- if not msg then
- self.queue = {}
- return true
- end
- local ok, err = self.pub_mq:publish(msg.pattern, msg.payload)
- if not ok then
- break
- end
- index = index + 1
- end
- if self.pub_mq then
- self.pub_mq:close()
- self.pub_mq = nil
- end
- end
-end
-
-local function mqtt_publish(self)
- local index = 1
- while 1 do
- if not self.pub_mq then
- local pub_mq, err = mq_login(self)
- if not pub_mq then
- return nil, err
- end
- self.pub_mq = pub_mq
- end
- while 1 do
- local msg = self.queue[index]
- if not msg then
- self.queue = {}
- return true
- end
- local ok = self.pub_mq:publish{topic = msg.pattern, payload = msg.payload, qos = 2}
- if not ok then
- break
- end
- index = index + 1
- end
- if self.pub_mq then
- self.pub_mq:close()
- self.pub_mq = nil
- end
- end
-end
-
--- 发布消息
-function mq:emit(pattern, data)
- if type(pattern) ~= 'string' or pattern == '' then
- return nil, "publish pattern error."
- end
- if type(data) ~= 'string' or data == '' then
- return nil, "publish string error."
- end
- if not self.queue then
- self.queue = {{pattern = pattern, payload = data}}
- else
- self.queue[#self.queue + 1] = {pattern = pattern, payload = data}
- end
- if self.type == 'redis' then
- return redis_publish(self)
- elseif self.type == 'mqtt' then
- return mqtt_publish(self)
- end
- return error("mq publish error: 目前仅支持redis/mqtt协议.")
-end
-
--- 订阅消息
-function mq:on(pattern, func)
- if type(pattern) ~= 'string' or pattern == '' then
- return nil, "subscribe pattern error."
- end
- if type(func) ~= 'function' then
- return nil, "subscribe func error."
- end
- self.func = func -- 回调处理函数
- self.pattern = pattern -- 监听规则
- if self.type == 'redis' then
- return redis_subscribe(self)
- elseif self.type == 'mqtt' then
- return mqtt_subscribe(self)
- end
- return error("mq subscribe error: 目前仅支持redis/mqtt协议.")
-end
-
--- 关闭消息队列监听
-function mq:close()
- if self.sub_mq then
- self.sub_mq:close()
- self.sub_mq = nil
- end
- if self.pub_mq then
- self.pub_mq:close()
- self.pub_mq = nil
- end
-end
-
-return mq
diff --git a/lualib/MQ/mqtt.lua b/lualib/MQ/mqtt.lua
new file mode 100644
index 00000000..153af41f
--- /dev/null
+++ b/lualib/MQ/mqtt.lua
@@ -0,0 +1,134 @@
+local class = require "class"
+local mqtt = require "protocol.mqtt"
+
+local logging = require "logging"
+local Log = logging:new{dump = true, path = 'MQ-mqtt'}
+
+local cf = require "cf"
+local cf_self = cf.self
+local cf_fork = cf.fork
+local cf_sleep = cf.sleep
+local cf_wait = cf.wait
+local cf_wakeup = cf.wakeup
+
+local ipairs = ipairs
+local type = type
+local insert = table.insert
+local random = math.random
+local fmt = string.format
+
+local mq = class('mqtt-mq')
+
+function mq:ctor (opt)
+ self.host = opt.host
+ self.port = opt.port
+ self.username = opt.username
+ self.password = opt.password
+ self.keepalive = opt.keepalive
+ self.patterns = {}
+ self.subsribes = {}
+ self.queue = {}
+end
+
+local function _login (opt)
+ local times = 1
+ while 1 do
+ local mq = mqtt:new {
+ id = opt.id or fmt('luamqtt-cf-v1-%X', random(1, 0xFFFFFFFF)),
+ host = opt.host,
+ port = opt.port,
+ clean = true,
+ keep_alive = opt.keepalive,
+ auth = {
+ username = opt.username,
+ password = opt.password,
+ },
+ }
+ local ok, err = mq:connect()
+ if ok then
+ return mq
+ end
+ Log:WARN("第"..times.."次连接MQ(mqtt)失败:"..(err or "未知错误."))
+ times = times + 1
+ mq:close()
+ cf_sleep(3)
+ end
+end
+
+-- 订阅事件循环
+local function subscribe (self, pattern, func)
+ local mq = _login(self)
+ self.subsribes[#self.subsribes+1] = mq
+ return mq:subscribe({qos = 2, topic = pattern}, func)
+end
+
+-- 发布事件循环
+local function publish (self, pattern, data)
+ if #self.queue == 0 then
+ self.queue[#self.queue + 1] = {pattern = pattern, data = data, co = cf_self()}
+ cf_fork(function (...)
+ for _, msg in ipairs(self.queue) do
+ if not self.closed and self.emiter then
+ cf_wakeup(msg.co, self.emiter:publish{topic = msg.pattern, payload = msg.data, qos = 2})
+ end
+ end
+ self.queue = {}
+ end)
+ return cf_wait()
+ end
+ insert(self.queue, {pattern = pattern, data = data, co = cf_self()})
+ return cf_wait()
+end
+
+-- 订阅
+function mq:on (pattern, func)
+ if type(pattern) ~= 'string' or pattern == '' then
+ return nil, "订阅消息失败: 错误的pattern类型"
+ end
+ if type(func) ~= 'function' then
+ return nil, "订阅消息失败: 错误的func类型"
+ end
+ for _, patt in ipairs(self.patterns) do
+ if patt == pattern then
+ return nil, '禁止重复订阅相同的频道'
+ end
+ end
+ self.patterns[#self.patterns + 1] = pattern
+ return subscribe(self, pattern, func)
+end
+
+-- 发布
+function mq:emit (pattern, data)
+ if type(pattern) ~= 'string' or pattern == '' then
+ return nil, "推送消息失败: 错误的pattern类型"
+ end
+ if type(data) ~= 'string' or data == '' then
+ return nil, "推送消息失败: 错误的data类型"
+ end
+ if not self.emiter then
+ self.emiter = _login(self)
+ end
+ return publish(self, pattern, data)
+end
+
+-- 启动MQ服务
+function mq:start()
+ return cf.wait()
+end
+
+-- 关闭MQ服务
+function mq:close ()
+ self.closed = true
+ if self.emiter then
+ self.emiter:close()
+ self.emiter = nil
+ end
+ if self.subsribes then
+ for _, sub in ipairs(self.subsribes) do
+ sub:close()
+ end
+ self.subsribes = nil
+ end
+end
+
+return mq
diff --git a/lualib/MQ/redis.lua b/lualib/MQ/redis.lua
new file mode 100644
index 00000000..b25f2949
--- /dev/null
+++ b/lualib/MQ/redis.lua
@@ -0,0 +1,126 @@
+local class = require "class"
+local redis = require "protocol.redis"
+
+local logging = require "logging"
+local Log = logging:new{dump = true, path = 'MQ-redis'}
+
+local cf = require "cf"
+local cf_self = cf.self
+local cf_fork = cf.fork
+local cf_sleep = cf.sleep
+local cf_wait = cf.wait
+local cf_wakeup = cf.wakeup
+
+local ipairs = ipairs
+local type = type
+local insert = table.insert
+
+local mq = class('redis-mq')
+
+function mq:ctor (opt)
+ self.host = opt.host
+ self.port = opt.port
+ self.auth = opt.auth
+ self.db = opt.db
+ self.patterns = {}
+ self.subsribes = {}
+ self.queue = {}
+end
+
+local function _login (opt)
+ local times = 1
+ while 1 do
+ local mq = redis:new {
+ host = opt.host,
+ port = opt.port,
+ auth = opt.auth,
+ db = opt.db,
+ }
+ local ok, err = mq:connect()
+ if ok then
+ return mq
+ end
+ Log:WARN("第"..times.."次连接MQ(redis)失败:"..(err or "未知错误."))
+ times = times + 1
+ mq:close()
+ cf_sleep(3)
+ end
+end
+
+-- 订阅事件循环
+local function subscribe (self, pattern, func)
+ local mq = _login(self)
+ self.subsribes[#self.subsribes+1] = mq
+ return mq:subscribe(pattern, func)
+end
+
+-- 发布事件循环
+local function publish (self, pattern, data)
+ if #self.queue == 0 then
+ self.queue[#self.queue + 1] = {pattern = pattern, data = data, co = cf_self()}
+ cf_fork(function (...)
+ for _, msg in ipairs(self.queue) do
+ if not self.closed and self.emiter then
+ cf_wakeup(msg.co, self.emiter:publish(msg.pattern, msg.data))
+ end
+ end
+ self.queue = {}
+ end)
+ return cf_wait()
+ end
+ insert(self.queue, {pattern = pattern, data = data, co = cf_self()})
+ return cf_wait()
+end
+
+-- 订阅
+function mq:on (pattern, func)
+ if type(pattern) ~= 'string' or pattern == '' then
+ return nil, "订阅消息失败: 错误的pattern类型"
+ end
+ if type(func) ~= 'function' then
+ return nil, "订阅消息失败: 错误的func类型"
+ end
+ for _, patt in ipairs(self.patterns) do
+ if patt == pattern then
+ return nil, '禁止重复订阅相同的频道'
+ end
+ end
+ self.patterns[#self.patterns + 1] = pattern
+ return subscribe(self, pattern, func)
+end
+
+-- 发布
+function mq:emit (pattern, data)
+ if type(pattern) ~= 'string' or pattern == '' then
+ return nil, "推送消息失败: 错误的pattern类型"
+ end
+ if type(data) ~= 'string' or data == '' then
+ return nil, "推送消息失败: 错误的data类型"
+ end
+ if not self.emiter then
+ self.emiter = _login(self)
+ end
+ return publish(self, pattern, data)
+end
+
+-- 启动MQ服务
+function mq:start()
+ return cf.wait()
+end
+
+-- 关闭MQ服务
+function mq:close ()
+ self.closed = true
+ if self.emiter then
+ self.emiter:close()
+ self.emiter = nil
+ end
+ if self.subsribes then
+ for _, sub in ipairs(self.subsribes) do
+ sub:close()
+ end
+ self.subsribes = nil
+ end
+end
+
+return mq
diff --git a/lualib/MQ/stomp.lua b/lualib/MQ/stomp.lua
new file mode 100644
index 00000000..7d76612c
--- /dev/null
+++ b/lualib/MQ/stomp.lua
@@ -0,0 +1,112 @@
+local class = require "class"
+local stomp = require "protocol.stomp"
+
+local logging = require "logging"
+local Log = logging:new{dump = true, path = 'MQ-stomp'}
+
+local cf = require "cf"
+local cf_fork = cf.fork
+local cf_sleep = cf.sleep
+
+local ipairs = ipairs
+local type = type
+local insert = table.insert
+
+local mq = class('stomp-mq')
+
+function mq:ctor (opt)
+ self.host = opt.host
+ self.port = opt.port
+ self.vhost = opt.vhost
+ self.header = opt.header
+ self.username = opt.username
+ self.password = opt.password
+ self.patterns = {}
+ self.subsribes = {}
+end
+
+local function _login (opt)
+ local times = 1
+ while 1 do
+ local mq = stomp:new {
+ host = opt.host, port = opt.port, vhost = opt.vhost, auth = opt.auth,
+ header = opt.header, username = opt.username, password = opt.password,
+ }
+ local ok, err = mq:connect()
+ if ok then
+ return mq
+ end
+ Log:WARN("第"..times.."次连接MQ(stomp)失败:"..(err or "未知错误."))
+ times = times + 1
+ mq:close()
+ cf_sleep(3)
+ end
+end
+
+-- 订阅事件循环
+local function subscribe (self, pattern, func)
+ local m = _login(self)
+ insert(self.subsribes, m)
+ return m:subscribe(pattern, func)
+end
+
+-- 发布事件循环
+local function publish (self, pattern, data)
+ if not self.queue then
+ self.queue = {}
+ cf_fork(function ()
+ for _, msg in ipairs(self.queue) do
+ if self.closed or not self.emiter:publish(msg.pattern, msg.data) then
+ break
+ end
+ end
+ self.queue = nil
+ end)
+ end
+ insert(self.queue, {pattern = pattern, data = data})
+ return true
+end
+
+-- 订阅
+function mq:on (pattern, func)
+ assert((type(pattern) == 'string' and pattern ~= '') and type(func) == 'function', "[STOMP ERROR] : Invalid `pattern`/`func` type.")
+ for _, patt in ipairs(self.patterns) do
+ if patt == pattern then
+ return nil, '[STOMP ERROR] : already subscribe.'
+ end
+ end
+ insert(self.patterns, pattern)
+ -- self.patterns[#self.patterns + 1] = pattern
+ return subscribe(self, pattern, func)
+end
+
+-- 发布
+function mq:emit (pattern, data)
+ assert((type(pattern) == 'string' and pattern ~= '') and (type(data) == 'string' and data ~= ''), "[STOMP ERROR] : Invalid `pattern`/`data` type.")
+ if not self.emiter then
+ self.emiter = _login(self)
+ end
+ return publish(self, pattern, data)
+end
+
+-- 启动MQ服务
+function mq:start()
+ return cf.wait()
+end
+
+-- 关闭MQ服务
+function mq:close ()
+ self.closed = true
+ if self.emiter then
+ self.emiter:close()
+ self.emiter = nil
+ end
+ if self.subsribes then
+ for _, sub in ipairs(self.subsribes) do
+ sub:close()
+ end
+ self.subsribes = nil
+ end
+end
+
+return mq
diff --git a/lualib/README.md b/lualib/README.md
new file mode 100644
index 00000000..f178368b
--- /dev/null
+++ b/lualib/README.md
@@ -0,0 +1,39 @@
+## 内置库列表
+
+ * mail库
+
+ * csv库
+
+ * SDK库
+
+ * DB库(MySQL)
+
+ * Cache库(Redis)
+
+ * logging库
+
+ * httpc库
+
+ * MQ库
+
+ * crypt库
+
+ * webhook库
+
+ * json库
+
+ * msgpack库
+
+ * protobuf库
+
+ * template库
+
+ * cf库
+
+ * admin库
+
+ * xml库
+
+ * httpd库
+
+ * system库
\ No newline at end of file
diff --git a/lualib/admin/README.md b/lualib/admin/README.md
new file mode 100644
index 00000000..7c74f401
--- /dev/null
+++ b/lualib/admin/README.md
@@ -0,0 +1,7 @@
+## admin库
+
+ admin是cf内置的web后台管理模板库, 目的是为使用者快速构建后台基于web的管理系统.
+
+## 基准
+
+ 详情清参考[这里](https://candymi.github.io/LuaWeb/wiki/Benchmark.html)
diff --git a/lualib/admin/config.lua b/lualib/admin/config.lua
new file mode 100644
index 00000000..34b97317
--- /dev/null
+++ b/lualib/admin/config.lua
@@ -0,0 +1,12 @@
+local config = {
+ cdn = '/', -- 静态文件前缀地址
+ github = 'https://github.com/candymi/core_framework', -- 跳转地址
+ cache = false, -- 是否缓存模板
+ locale = "ZH-CN", -- 当前语言
+ display_lang = true, -- 默认显示语言标签
+ locales = require "admin.locales", -- 语言表
+ secure = 'cfadmin', -- 生成token的secure
+ cookie_timeout = 86400 -- Cookie超时时间
+}
+
+return config
diff --git a/lualib/admin/cookie.lua b/lualib/admin/cookie.lua
new file mode 100644
index 00000000..2cd7f34d
--- /dev/null
+++ b/lualib/admin/cookie.lua
@@ -0,0 +1,34 @@
+local config = require "admin.config"
+
+local Cookie = require "httpd.Cookie"
+local getCookie = Cookie.getCookie
+local setCookie = Cookie.setCookie
+local delCookie = Cookie.delCookie
+
+local os_time = os.time
+
+local Cookie = {}
+
+-- 登录页面需要初始化Cookie.
+function Cookie.init ()
+ local session = getCookie('CFTOKEN')
+ if session then
+ return delCookie("CFTOKEN")
+ end
+ local session = getCookie('CFLANG')
+ if not session then
+ setCookie("CFLANG", config.locale)
+ end
+end
+
+-- 设置Cookie
+function Cookie.setCookie (name, value)
+ return setCookie(name, value, config.cookie_timeout + os_time())
+end
+
+-- 获取Cookie
+function Cookie.getCookie (name)
+ return getCookie(name)
+end
+
+return Cookie
diff --git a/lualib/admin/db/README.md b/lualib/admin/db/README.md
new file mode 100644
index 00000000..9295f2b8
--- /dev/null
+++ b/lualib/admin/db/README.md
@@ -0,0 +1,9 @@
+# db
+
+## view
+
+ dashboard视图的sql
+
+## user
+
+ user的sql
diff --git a/lualib/admin/db/database.sql b/lualib/admin/db/database.sql
new file mode 100644
index 00000000..90a70f98
--- /dev/null
+++ b/lualib/admin/db/database.sql
@@ -0,0 +1,110 @@
+# ************************************************************
+# Sequel Pro SQL dump
+# Version 4541
+#
+# http://www.sequelpro.com/
+# https://github.com/sequelpro/sequelpro
+#
+# Host: 127.0.0.1 (MySQL 5.7.25)
+# Database: cfadmin
+# Generation Time: 2019-05-21 02:32:16 +0000
+# ************************************************************
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8mb4 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+# ----------------------------
+# Table structure for cfadmin_headers
+# ----------------------------
+CREATE TABLE IF NOT EXISTS `cfadmin_headers` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `name` varchar(255) NOT NULL COMMENT '头部名称',
+ `url` varchar(255) NOT NULL COMMENT '头部URL',
+ `create_at` int(11) unsigned NOT NULL COMMENT '创建时间',
+ `update_at` int(11) unsigned NOT NULL COMMENT '修改时间',
+ `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='顶部菜单表';
+
+# ----------------------------
+# Table structure for cfadmin_menus
+# ----------------------------
+CREATE TABLE IF NOT EXISTS `cfadmin_menus` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `parent` int(11) unsigned NOT NULL COMMENT '父菜单ID',
+ `name` varchar(255) NOT NULL COMMENT '菜单名称',
+ `url` varchar(255) DEFAULT NULL COMMENT '菜单链接',
+ `icon` char(255) DEFAULT NULL COMMENT '菜单图标',
+ `create_at` int(11) unsigned NOT NULL COMMENT '创建时间',
+ `update_at` int(11) unsigned NOT NULL COMMENT '更新时间',
+ `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志',
+ PRIMARY KEY (`id`),
+ KEY `parant_index` (`parent`) USING BTREE COMMENT '父ID索引'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='侧边菜单表';
+
+# ----------------------------
+# Table structure for cfadmin_permissions
+# ----------------------------
+CREATE TABLE IF NOT EXISTS `cfadmin_permissions` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `role_id` int(11) unsigned NOT NULL COMMENT '所属角色',
+ `menu_id` int(11) unsigned NOT NULL COMMENT '所属菜单',
+ `create_at` int(11) unsigned NOT NULL COMMENT '创建时间',
+ `update_at` int(11) unsigned NOT NULL COMMENT '修改时间',
+ `active` tinyint(4) unsigned NOT NULL COMMENT '是否启用',
+ PRIMARY KEY (`id`),
+ KEY `com_index` (`active`,`role_id`,`menu_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限表';
+
+# ----------------------------
+# Table structure for cfadmin_roles
+# ----------------------------
+CREATE TABLE IF NOT EXISTS `cfadmin_roles` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `name` varchar(255) NOT NULL COMMENT '角色名称',
+ `is_admin` tinyint(4) unsigned NOT NULL COMMENT '管理员标志',
+ `create_at` int(11) unsigned NOT NULL COMMENT '创建时间',
+ `update_at` int(1) unsigned NOT NULL COMMENT '修改时间',
+ `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色表';
+
+# ----------------------------
+# Table structure for cfadmin_tokens
+# ----------------------------
+CREATE TABLE IF NOT EXISTS `cfadmin_tokens` (
+ `uid` int(11) unsigned NOT NULL COMMENT '用户ID',
+ `name` varchar(255) NOT NULL COMMENT '用户名称',
+ `token` varchar(255) NOT NULL COMMENT '用户TOKEN',
+ `create_at` int(11) unsigned NOT NULL COMMENT '登录时间',
+ PRIMARY KEY (`uid`)
+) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COMMENT='用户Token表';
+
+# ----------------------------
+# Table structure for cfadmin_users
+# ----------------------------
+CREATE TABLE IF NOT EXISTS `cfadmin_users` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+ `name` varchar(255) NOT NULL COMMENT '用户名',
+ `username` varchar(255) NOT NULL COMMENT '用户账户',
+ `password` varchar(255) NOT NULL COMMENT '用户密码',
+ `email` varchar(255) NOT NULL COMMENT '用户邮箱',
+ `phone` bigint(11) unsigned NOT NULL COMMENT '用户手机',
+ `role` int(11) unsigned NOT NULL COMMENT '用户角色',
+ `create_at` int(11) unsigned NOT NULL COMMENT '创建时间',
+ `update_at` int(11) unsigned NOT NULL COMMENT '修改时间',
+ `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
+
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/lualib/admin/db/header.lua b/lualib/admin/db/header.lua
new file mode 100644
index 00000000..daced276
--- /dev/null
+++ b/lualib/admin/db/header.lua
@@ -0,0 +1,49 @@
+local toint = math.tointeger
+local os_time = os.time
+local fmt = string.format
+
+local header = {}
+
+-- header 列表
+function header.header_list (db, opt)
+ local limit = toint(opt.limit) or 10
+ local page = toint(opt.page) or 1
+ return db:query(fmt([[SELECT id, name, url, create_at, update_at FROM cfadmin_headers WHERE active = 1 ORDER BY id LIMIT %s, %s]], limit * (page - 1) , limit))
+end
+
+-- 获取指定header
+function header.get_header (db, id)
+ local ret = db:query(fmt([[ SELECT id, name, url FROM cfadmin_headers WHERE id = '%s' LIMIT 1]], id))
+ if not ret or #ret == 0 then
+ return
+ end
+ return ret[1]
+end
+
+-- header 总数
+function header.header_count (db)
+ return db:query([[SELECT count(id) AS count FROM cfadmin_headers WHERE active = 1]])[1]['count']
+end
+
+-- 是否存在此header
+function header.header_exists (db, id)
+ local ret = db:query(fmt([[SELECT id FROM cfadmin_headers WHERE id = '%s' AND active = 1 LIMIT 1]], id))
+ return ret and #ret > 0
+end
+
+-- 删除header
+function header.header_delete (db, id)
+ return db:query(fmt([[UPDATE cfadmin_headers SET active = '0', update_at = '%s' WHERE id = '%s' AND active = 1]], os_time(), id))
+end
+
+-- 增加header
+function header.header_add(db, opt)
+ return db:query(fmt([[INSERT INTO cfadmin_headers(`name`, `url`, `create_at`, `update_at`, `active`) VALUES('%s', '%s', '%s', '%s', 1)]], opt.name, opt.url, os_time(), os_time()))
+end
+
+-- 修改header
+function header.header_update (db, opt)
+ return db:query(fmt([[UPDATE cfadmin_headers SET name = '%s', url = '%s', update_at = '%s' WHERE id = '%s']], opt.name, opt.url, os_time(), opt.id))
+end
+
+return header
diff --git a/lualib/admin/db/init.lua b/lualib/admin/db/init.lua
new file mode 100644
index 00000000..9ecba344
--- /dev/null
+++ b/lualib/admin/db/init.lua
@@ -0,0 +1,43 @@
+local crypt = require "crypt"
+local config = require "admin.config"
+
+local log = require "logging"
+local Log = log:new({path = 'admin-db'})
+
+local fmt = string.format
+local os_time = os.time
+-- 作为初始化DB工作, 这个函数(must)只能运行一次.
+-- 一般情况下, 大家在设计完成后都会手动简历数据表并导入内容.
+-- 此文件仅作为作者调试与使用者开发使用, 不对此文件做任何其它保证.
+local create_admin = fmt([[
+INSERT INTO
+ `cfadmin_users`
+ (`id`, `name`, `username`, `password`, `email`, `phone`, `role`, `create_at`, `update_at`, `active`)
+ VALUES
+ ('1', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '1')]],
+'管理员', 'admin', crypt.sha1('admin', true), '869646063@qq.com', '13000000000', '1', os_time(), os_time())
+
+local create_admin_role = fmt([[
+INSERT INTO
+ cfadmin_roles
+ (`id`, `name`, `is_admin`, `create_at`, `update_at`, `active`)
+ VALUES
+ ('1', '管理员', '1', '%s', '%s', '1')
+]], os_time(), os_time())
+
+return function ()
+ local ret, err
+ local db = config.db
+ local now = os_time()
+ -- 初始化用户
+ ret, err = db:query(create_admin)
+ if not ret then
+ Log:ERROR(err)
+ end
+ -- 初始化角色
+ ret, err = db:query(create_admin_role)
+ if not ret then
+ Log:ERROR(err)
+ end
+ return true, '初始化完成'
+end
diff --git a/lualib/admin/db/menu.lua b/lualib/admin/db/menu.lua
new file mode 100644
index 00000000..f4199a51
--- /dev/null
+++ b/lualib/admin/db/menu.lua
@@ -0,0 +1,77 @@
+local toint = math.tointeger
+local fmt = string.format
+local concat = table.concat
+local os_time = os.time
+
+local menu = {}
+
+-- 菜单列表
+function menu.menu_list (db, opt)
+ local limit = toint(opt.limit) or 100
+ local page = toint(opt.page) or 1
+ return db:query(fmt([[SELECT id, parent, name, url, icon, create_at, update_at FROM cfadmin_menus WHERE active = 1 LIMIT %u, %u]], limit * (page - 1), limit))
+end
+
+-- 菜单名已存在
+function menu.menu_name_exists (db, name)
+ local ret = db:query(fmt([[SELECT name FROM cfadmin_menus WHERE name = '%s' AND active = 1]], name))
+ if ret and #ret > 0 then
+ return true
+ end
+ return false
+end
+
+-- 菜单信息
+function menu.menu_info (db, id)
+ return db:query(fmt([[SELECT id, name, url, icon FROM cfadmin_menus WHERE id = '%s' AND active = 1]], id))[1]
+end
+
+-- 添加菜单菜单
+function menu.menu_add (db, opt)
+ local now = os_time()
+ -- if opt.id > 0 then -- 二级菜单增加后需要保留一级菜单
+ -- db:query(fmt([[UPDATE cfadmin_menus SET URL = 'NULL' WHERE id = '%s' AND active = 1]], opt.id))
+ -- end
+ return db:query(fmt([[INSERT INTO cfadmin_menus(`parent`, `name`, `url`, `icon`, `create_at`, `update_at`, `active`) VALUES('%s', '%s', '%s', '%s', '%s', '%s', 1)]], opt.id, opt.name, opt.url, opt.icon, now, now))
+end
+
+-- 更新菜单
+function menu.menu_update (db, opt)
+ local ret = db:query(fmt([[SELECT id FROM cfadmin_menus WHERE parent == '%s' AND active = 1]], opt.id))
+ return db:query(fmt([[UPDATE cfadmin_menus SET name = '%s', url = '%s', icon = '%s' WHERE id = '%s' AND active = 1]], opt.name, ret and #ret > 0 and "NULL" or opt.url, opt.icon, opt.id))
+end
+
+-- dtree专用结构
+function menu.menu_tree (db)
+ local menus = db:query([[SELECT id, parent AS parentId, name AS title FROM cfadmin_menus WHERE active = 1]])
+ for _, menu in ipairs(menus) do
+ menu.checkArr = "0"
+ end
+ return menus
+end
+
+-- 删除菜单与下属子菜单
+function menu.menu_delete (db, id)
+ local id_list = {}
+ local menus = db:query(fmt([[SELECT id FROM cfadmin_menus WHERE parent = '%s' AND active = 1]], id))
+ if menus and #menus > 0 then
+ for _, menu in ipairs(menus) do
+ id_list[#id_list+1] = menu.id
+ end
+ local subs = db:query(fmt([[SELECT id FROM cfadmin_menus WHERE parent IN (%s) AND active = 1]], concat(id_list, ', ')))
+ if subs and #subs > 0 then
+ for _, sub in ipairs(subs) do
+ id_list[#id_list+1] = sub.id
+ end
+ end
+ end
+ id_list[#id_list+1] = id
+ local now = os_time()
+ local list = concat(id_list, ', ')
+ -- 删除menu
+ db:query(fmt([[UPDATE cfadmin_menus SET active = '0', update_at = '%s' WHERE id IN (%s) AND active = 1]], now, list))
+ -- 删除role关联permissions
+ db:query(fmt([[UPDATE cfadmin_permissions SET active = '0', update_at = '%s' WHERE menu_id IN (%s) AND active = 1]], now, list))
+end
+
+return menu
diff --git a/lualib/admin/db/permission.lua b/lualib/admin/db/permission.lua
new file mode 100644
index 00000000..318f779c
--- /dev/null
+++ b/lualib/admin/db/permission.lua
@@ -0,0 +1,25 @@
+local fmt = string.format
+
+local permission = {}
+
+-- 用户是否有此菜单的权限
+function permission.user_have_menu_permission (db, uid, url)
+ -- 查询用户Role ID
+ local uinfo = db:query(fmt([[SELECT id, role AS role_id FROM cfadmin_users WHERE `cfadmin_users`.id = %u AND `cfadmin_users`.active = 1 LIMIT 1]], uid))[1]
+ if type(uinfo) ~= 'table' then
+ return false
+ end
+ -- 查询菜单Menu ID
+ local minfo = db:query(fmt([[SELECT * FROM cfadmin_menus WHERE `cfadmin_menus`.url = '%s' AND `cfadmin_menus`.active = 1 LIMIT 1]], url))[1]
+ if type(minfo) ~= 'table' then
+ return true
+ end
+ -- 检查权限
+ local role, err = db:query(fmt([[SELECT * FROM cfadmin_permissions p WHERE p.`active` = 1 AND p.`role_id` = %u AND p.`menu_id` = %u LIMIT 1]], uinfo.role_id, minfo.id))
+ if type(role) == 'table' then
+ return role[1]
+ end
+ return role, err
+end
+
+return permission
diff --git a/lualib/admin/db/role.lua b/lualib/admin/db/role.lua
new file mode 100644
index 00000000..fd2ed56f
--- /dev/null
+++ b/lualib/admin/db/role.lua
@@ -0,0 +1,96 @@
+local toint = math.tointeger
+local concat = table.concat
+local os_time = os.time
+local os_date = os.date
+local fmt = string.format
+
+local role = {}
+
+-- 角色列表
+function role.role_list (db, opt)
+ local limit = toint(opt.limit) or 10
+ local page = toint(opt.page) or 1
+ local roles = db:query(fmt([[SELECT id, name, create_at, update_at FROM cfadmin_roles WHERE active = '1' ORDER BY id LIMIT %s, %s]], limit * (page - 1) , limit))
+ if not roles then
+ return
+ end
+ for _, r in ipairs(roles) do
+ r.create_at = os_date("%F %X", r.create_at)
+ r.update_at = os_date("%F %X", r.update_at)
+ end
+ return roles
+end
+
+-- 计算角色数量
+function role.role_count (db)
+ return db:query([[SELECT count(id) AS count FROM cfadmin_roles WHERE active = '1']])[1]['count']
+end
+
+-- 角色对应权限
+function role.role_permissions (db, id)
+ return db:query(fmt([[SELECT role_id, menu_id FROM cfadmin_permissions WHERE role_id = '%s' AND active = '1']], id))
+end
+
+-- 角色名已存在
+function role.role_name_exists (db, name)
+ local ret = db:query(fmt([[SELECT id, name FROM cfadmin_roles WHERE name = '%s' AND active = '1' LIMIT 1]], name))
+ if ret and #ret > 0 then
+ return ret[1]
+ end
+ return false
+end
+
+-- 角色id已存在
+function role.role_id_exists (db, id)
+ local ret = db:query(fmt([[SELECT id, name FROM cfadmin_roles WHERE id = '%s' AND active = '1' LIMIT 1]], id))
+ if ret and #ret > 0 then
+ return ret[1]
+ end
+ return false
+end
+
+-- 添加角色
+function role.role_add (db, opt)
+ local now = os_time()
+ db:query(fmt([[INSERT INTO cfadmin_roles(`name`, `is_admin`, `create_at`, `update_at`, `active`) VALUES('%s', '0', '%s', '%s', '1')]], opt.name, now, now))
+ if opt.permissions then
+ local id = db:query(fmt([[SELECT id FROM cfadmin_roles WHERE name = '%s' AND active = '1' LIMIT 1]], opt.name))[1]['id']
+ local tab = {}
+ local SQL = [[INSERT INTO cfadmin_permissions(`role_id`, `menu_id`, `create_at`, `update_at`, `active`) VALUES]]
+ for _, permission in ipairs(opt.permissions) do
+ tab[#tab+1] = '('..concat({id, permission.menu_id, now, now, 1}, ', ')..')'
+ end
+ db:query(SQL..concat(tab, ', '))
+ end
+end
+
+-- 删除角色关联数据
+function role.role_delete (db, id)
+ local now = os_time()
+ -- 删除角色
+ local ok = db:query(fmt([[UPDATE cfadmin_roles SET active = '0', update_at = '%s' WHERE id = '%s' AND active = '1']], now, id))
+ if not ok then
+ return
+ end
+ -- 删除角色对应的权限
+ local ok = db:query(fmt([[UPDATE cfadmin_permissions SET active = '0', update_at = '%s' WHERE role_id = '%s' AND active = '1']], now, id))
+ if not ok then
+ return
+ end
+ return true
+end
+
+-- 更新role相关数据
+function role.role_update(db, opt)
+ local now = os_time()
+ db:query(fmt([[UPDATE cfadmin_roles SET name = '%s', update_at = '%s' where id = '%s' AND active = '1']], opt.name, now, opt.id))
+ db:query(fmt([[UPDATE cfadmin_permissions SET active = '0', update_at = '%s' WHERE role_id = '%s']], now, opt.id))
+ local tab = {}
+ local SQL = [[INSERT INTO cfadmin_permissions(`role_id`, `menu_id`, `create_at`, `update_at`, `active`) VALUES]]
+ for _, permission in ipairs(opt.permissions) do
+ tab[#tab+1] = '('..concat({opt.id, permission.menu_id, now, now, 1}, ', ')..')'
+ end
+ db:query(SQL..concat(tab, ', '))
+end
+
+return role
diff --git a/lualib/admin/db/token.lua b/lualib/admin/db/token.lua
new file mode 100644
index 00000000..6f9e204d
--- /dev/null
+++ b/lualib/admin/db/token.lua
@@ -0,0 +1,68 @@
+
+local fmt = string.format
+local os_time = os.time
+
+local token = {}
+
+-- 写入Token
+function token.token_add (db, uid, name, token)
+ return db:query(fmt(
+ [[
+ INSERT INTO
+ `cfadmin_tokens`(`uid`, `name`, `token`, `create_at`)
+ VALUES
+ ('%s', '%s', '%s', '%s')
+ ON DUPLICATE KEY UPDATE `token` = '%s', `name` = '%s', `create_at` = '%s'
+ ]],
+ uid, name, token, os_time(), token, name, os_time()))
+end
+
+-- 删除Token
+function token.token_delete (db, id, tk)
+ return db:query(fmt([[DELETE FROM cfadmin_tokens WHERE uid = '%s' or token = '%s' LIMIT 1]], id, tk))
+end
+
+-- Token 是否存在
+function token.token_exists (db, token)
+ local ret = db:query(fmt([[SELECT uid, name, token FROM cfadmin_tokens WHERE token = '%s']], token:gsub("['\\]", "")))
+ if not ret or #ret == 0 then
+ return
+ end
+ return ret[1]
+end
+
+-- 根据token查用户信息
+function token.token_to_userinfo (db, token)
+ local tokens, err = db:query(fmt([[
+ SELECT
+ `cfadmin_users`.id,
+ `cfadmin_users`.name,
+ `cfadmin_users`.username,
+ `cfadmin_users`.password,
+ `cfadmin_tokens`.token,
+ `cfadmin_users`.role,
+ `cfadmin_users`.name AS role_name,
+ `cfadmin_roles`.is_admin,
+ `cfadmin_users`.email,
+ `cfadmin_users`.phone,
+ `cfadmin_users`.create_at,
+ `cfadmin_users`.update_at
+ FROM
+ cfadmin_users, cfadmin_tokens, cfadmin_roles
+ WHERE
+ `cfadmin_tokens`.uid = `cfadmin_users`.id AND
+ `cfadmin_roles`.id = `cfadmin_users`.role AND
+ `cfadmin_tokens`.token = '%s'
+ LIMIT 1]], token:gsub("['\\]", "")))
+ if type(tokens) ~= 'table'then
+ return nil, err
+ end
+ return tokens[1]
+end
+
+-- 删除其他用户token信息
+function token.flush_all(db, uid)
+ return db:query(fmt([[DELETE FROM cfadmin_tokens WHERE uid != %u]], uid))
+end
+
+return token
diff --git a/lualib/admin/db/user.lua b/lualib/admin/db/user.lua
new file mode 100644
index 00000000..1fde7b03
--- /dev/null
+++ b/lualib/admin/db/user.lua
@@ -0,0 +1,156 @@
+local toint = math.tointeger
+local tostring = tostring
+local os_time = os.time
+local fmt = string.format
+
+local user = {}
+
+-- 用户列表
+function user.user_list (db, opt)
+ local limit = toint(opt.limit) or 10
+ local page = toint(opt.page) or 1
+ return db:query(fmt([[
+ SELECT
+ `cfadmin_users`.id,
+ `cfadmin_users`.name,
+ `cfadmin_users`.username,
+ `cfadmin_roles`.name AS role_name,
+ `cfadmin_users`.email,
+ `cfadmin_users`.phone,
+ `cfadmin_users`.create_at,
+ `cfadmin_users`.update_at,
+ `cfadmin_users`.active
+ FROM cfadmin_users, cfadmin_roles
+ WHERE
+ `cfadmin_roles`.id = `cfadmin_users`.role AND `cfadmin_users`.active = 1
+ LIMIT %s, %s
+ ]], limit * (page - 1), limit))
+end
+
+-- 模糊查找用户列表
+function user.find_user (db, opt)
+ local limit = toint(opt.limit) or 10
+ local page = toint(opt.page) or 1
+ local condition
+ if opt.condition == 'id' or opt.condition == 'email' or opt.condition == 'phone' then
+ condition = fmt("`cfadmin_users`.`%s` = '%s'", opt.condition, opt.value)
+ else
+ condition = fmt("`cfadmin_users`.`%s` LIKE '%%%s%%'", opt.condition, opt.value)
+ end
+ local counts = db:query(fmt([[SELECT COUNT(id) as count FROM cfadmin_users WHERE active = 1 AND %s ]], condition))
+ if not counts or not counts[1] then
+ return
+ end
+ local data_sql = fmt([[
+ SELECT
+ `cfadmin_users`.id,
+ `cfadmin_users`.name,
+ `cfadmin_users`.username,
+ `cfadmin_roles`.name AS role_name,
+ `cfadmin_users`.email,
+ `cfadmin_users`.phone,
+ `cfadmin_users`.create_at,
+ `cfadmin_users`.update_at,
+ `cfadmin_users`.active
+ FROM cfadmin_users, cfadmin_roles
+ WHERE
+ `cfadmin_roles`.id = `cfadmin_users`.role AND `cfadmin_users`.active = 1 AND %s
+ ORDER BY `cfadmin_users`.id LIMIT %s, %s
+ ]], condition, limit * (page - 1), limit)
+ return db:query(data_sql), counts[1]['count']
+end
+
+-- 用户总数
+function user.user_count (db)
+ return db:query([[SELECT count(id) AS count FROM cfadmin_users WHERE active = '1']])[1]['count']
+end
+
+-- 用户是否存在
+function user.user_exists (db, username, uid)
+ local condition
+ if username then
+ condition = fmt([[`cfadmin_users`.username = '%s']], username:gsub("['\\]", ""))
+ elseif uid then
+ condition = fmt([[`cfadmin_users`.id = '%s']], tostring(uid):gsub("['\\]", ""))
+ else
+ return
+ end
+ local user, err = db:query(fmt([[
+ SELECT
+ `cfadmin_users`.id,
+ `cfadmin_users`.name,
+ `cfadmin_users`.username,
+ `cfadmin_users`.password
+ FROM cfadmin_users
+ WHERE
+ `cfadmin_users`.active = '1' AND %s
+ LIMIT 1]], condition))
+ if not user then
+ return nil, err
+ end
+ return user[1]
+end
+
+-- 用户名或者登录名是否存在
+function user.user_name_or_username_exists (db, name, username)
+ local ret = db:query(fmt([[SELECT name, username FROM cfadmin_users WHERE active = '1' AND (name = '%s' OR username = '%s')]], name, username))
+ if ret and #ret > 0 then
+ return true
+ end
+ return false
+end
+
+-- 用户信息
+function user.user_info (db, uid)
+ local ret = db:query(fmt([[
+ SELECT
+ `cfadmin_users`.id,
+ `cfadmin_users`.name,
+ `cfadmin_users`.username,
+ `cfadmin_users`.password,
+ `cfadmin_roles`.name AS role_name,
+ `cfadmin_roles`.is_admin,
+ `cfadmin_users`.role,
+ `cfadmin_users`.phone,
+ `cfadmin_users`.email
+ FROM
+ cfadmin_users, cfadmin_roles
+ WHERE
+ `cfadmin_users`.role = `cfadmin_roles`.id AND `cfadmin_users`.active = '1' AND `cfadmin_users`.id = '%s'
+ LIMIT 1]], uid))
+ return ret[1]
+end
+
+-- 添加用户
+function user.user_add (db, opt)
+ local now = os_time()
+ return db:query(fmt([[
+ INSERT INTO cfadmin_users(`name`, `username`, `password`, `role`, `email`, `phone`, `create_at`, `update_at`, `active`)
+ VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '1')
+ ]], opt.name, opt.username, opt.password, opt.role, opt.email, opt.phone, now, now))
+end
+
+-- 删除用户
+function user.user_delete (db, uid)
+ return db:query(fmt([[UPDATE cfadmin_users SET active = '0', update_at = '%s' WHERE id = '%s']], os_time(), uid))
+end
+
+-- 更新用户信息 1
+function user.user_update (db, opt)
+ return db:query(fmt([[
+ UPDATE cfadmin_users
+ SET name = '%s', username = '%s', password = '%s', role = '%s', email = '%s', phone = '%s', update_at = '%s' WHERE id = '%s'
+ ]], opt.name, opt.username, opt.password, opt.role, opt.email, opt.phone, os_time(), opt.id))
+end
+
+-- 更新用户密码
+function user.user_update_password (db, opt)
+ return db:query(fmt([[UPDATE cfadmin_users SET password = '%s', update_at = '%s' WHERE id = '%s' AND active = '1']], opt.password, os_time(), opt.id))
+end
+
+-- 更新用户信息 2
+function user.user_update_info (db, opt)
+ return db:query(fmt([[UPDATE cfadmin_users SET name = '%s', phone = '%s', email = '%s', update_at = '%s' WHERE id = '%s' AND active = '1']], opt.name, opt.phone, opt.email, os_time(), opt.id))
+end
+
+return user
diff --git a/lualib/admin/db/view.lua b/lualib/admin/db/view.lua
new file mode 100644
index 00000000..34a6e867
--- /dev/null
+++ b/lualib/admin/db/view.lua
@@ -0,0 +1,13 @@
+local view = {}
+
+-- 获取顶部栏
+function view.get_headers (db)
+ return db:query([[SELECT id, name, url FROM cfadmin_headers WHERE active = 1 ORDER BY `id`]])
+end
+
+-- 获取菜单栏
+function view.get_menus (db)
+ return db:query([[SELECT id, parent, name, url, icon, create_at, update_at FROM cfadmin_menus WHERE active = 1 ORDER BY `id`]])
+end
+
+return view
diff --git a/lualib/admin/html/404.html b/lualib/admin/html/404.html
new file mode 100644
index 00000000..60bca51f
--- /dev/null
+++ b/lualib/admin/html/404.html
@@ -0,0 +1,29 @@
+
+
+
+
+ {* locale['error.404.title'] *}
+
+
+
+
+
+
+
+
+
+
+
+]]
+
+
+
+return pages
diff --git a/lualib/protocol/http/parser.lua b/lualib/protocol/http/parser.lua
new file mode 100644
index 00000000..ec415a41
--- /dev/null
+++ b/lualib/protocol/http/parser.lua
@@ -0,0 +1,36 @@
+local httpparser = require "lhttpparser"
+local PARSER_HTTP_REQUEST = httpparser.parser_http_request
+local PARSER_HTTP_RESPONSE = httpparser.parser_http_response
+local RESPONSE_CHUNKED_PARSER = httpparser.parser_response_chunked
+
+local pcall = pcall
+
+local http_parser = {}
+
+function http_parser.PARSER_HTTP_REQUEST (buffer)
+ local ok, method, path, version, header = pcall(PARSER_HTTP_REQUEST, buffer)
+ if not ok then
+ return nil
+ end
+ return method, path, version, header
+end
+
+-- 解析http回应
+function http_parser.PARSER_HTTP_RESPONSE (buffer)
+ local ok, version, code, status, header = pcall(PARSER_HTTP_RESPONSE, buffer)
+ if not ok then
+ return nil
+ end
+ return version, code, status, header
+end
+
+-- 解析回应chunked
+function http_parser.RESPONSE_CHUNKED_PARSER (data)
+ local ok, data, pos = pcall(RESPONSE_CHUNKED_PARSER, data)
+ if not ok then
+ return nil, -1
+ end
+ return data, pos
+end
+
+return http_parser
\ No newline at end of file
diff --git a/lualib/protocol/http/ua.lua b/lualib/protocol/http/ua.lua
new file mode 100644
index 00000000..d14736cc
--- /dev/null
+++ b/lualib/protocol/http/ua.lua
@@ -0,0 +1,62 @@
+local random = math.random
+
+local ua = {
+ -- Windows
+ "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0",
+ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36",
+ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER",
+ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0",
+ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3756.400 QQBrowser/10.5.4039.400",
+ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.4098.3 Safari/537.36",
+ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3947.100 Safari/537.36 2345Explorer/10.7.0.20186",
+ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36 Maxthon/5.3.8.2000",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36 OPR/68.0.3618.63",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.66",
+ -- Apple
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:86.0) Gecko/20100101 Firefox/86.0",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:85.0) Gecko/20100101 Firefox/85.0",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:84.0) Gecko/20100101 Firefox/84.0",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:83.0) Gecko/20100101 Firefox/83.0",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:82.0) Gecko/20100101 Firefox/82.0",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:79.0) Gecko/20100101 Firefox/79.0",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:78.0) Gecko/20100101 Firefox/78.0",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:77.0) Gecko/20100101 Firefox/77.0",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.88 Safari/537.36",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.87 Safari/537.36",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.86 Safari/537.36",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.2 Safari/605.1.15 QQBrowserLite/1.2.9",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15 QQBrowserLite/1.2.7",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.0 Safari/605.1.15 QQBrowserLite/1.2.5",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.0 Safari/605.1.15 QQBrowserLite/1.2.9",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.2 Safari/605.1.15 QQBrowserLite/1.2.7",
+}
+
+function ua.get_user_agent()
+ return ua[random(1, #ua)]
+end
+
+return ua
diff --git a/lualib/protocol/mqtt/bit.lua b/lualib/protocol/mqtt/bit.lua
index 70e28c87..895d9211 100644
--- a/lualib/protocol/mqtt/bit.lua
+++ b/lualib/protocol/mqtt/bit.lua
@@ -1,9 +1,4 @@
-- wrapper around BitOp module
-
-if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" or type(jit) == "table" then
- return require("bit")
-else
- return require("protocol.mqtt.bit53")
-end
+return require("protocol.mqtt.bit53")
-- vim: ts=4 sts=4 sw=4 noet ft=lua
diff --git a/lualib/protocol/mqtt/init.lua b/lualib/protocol/mqtt/init.lua
index b3e8c03a..8f6fc489 100644
--- a/lualib/protocol/mqtt/init.lua
+++ b/lualib/protocol/mqtt/init.lua
@@ -7,10 +7,10 @@ local library_version = "1.4.2"
local require = require
local string = require "string"
-local calss = require "class"
+local class = require "class"
local tcp = require "internal.TCP"
local Co = require "internal.Co"
-local log = require "log"
+local log = require "logging"
local protocol = require "protocol.mqtt.protocol"
local protocol4 = require "protocol.mqtt.protocol4"
local co = require "internal.Co"
@@ -23,6 +23,8 @@ local make_packet4 = protocol4.make_packet
local parse_packet4 = protocol4.parse_packet
local connack_return_code = protocol4.connack_return_code
+local Log = log:new({ dump = true, path = 'protocol-MQTT'})
+
-- cache to locals
local type = type
local pairs = pairs
@@ -34,7 +36,7 @@ local str_match = string.match
local str_format = string.format
local str_gsub = string.gsub
local tbl_remove = table.remove
-local co_spwan = Co.spwan
+local co_spawn = Co.spawn
-- Empty function to do nothing on MQTT client events
local empty_func = function() end
@@ -54,21 +56,22 @@ function client:ctor(opt)
end
function client:subscribe(opt, func)
- local args = { type = packet_type.SUBSCRIBE, subscriptions = {opt} }
self.handle = func
- self:_send_packet(args)
+ local args = { type = packet_type.SUBSCRIBE, subscriptions = {opt} }
+ local ok, err = self:_send_packet(args)
+ if not ok then
+ return false, 'SUBSCRIBE send failed'
+ end
local ok, err = self:_wait_packet_exact{type=packet_type.SUBACK, packet_id=args.packet_id}
if not ok then
return false, 'SUBSCRIBE wait for SUBACK failed'
end
- co_spwan(function ( ... )
- local co_current = Co.self()
+ co_spawn(function ( ... )
local time = os_time()
local timer = Timer.at(self.keep_alive, function ( ... )
if os_time() >= time + self.keep_alive then
- return co.wakeup(co_current)
+ return self:ping()
end
- return self:ping()
end)
while 1 do
local packet, perr = self:_wait_packet_queue()
@@ -79,7 +82,7 @@ function client:subscribe(opt, func)
end
local ok, err = pcall(self.handle, nil)
if not ok then
- log.error(err)
+ Log:ERROR(err)
end
return false, 'waiting for the next packet failed'
end
@@ -87,16 +90,17 @@ function client:subscribe(opt, func)
if packet.type == packet_type.PUBLISH then
local ok, err = pcall(self.handle, packet)
if not ok then
- log.error(err)
+ Log:ERROR(err)
end
self:acknowledge(packet)
elseif packet.type == packet_type.PUBACK then
self:acknowledge(packet)
-- elseif packet.type == packet_type.PINGRESP then
- -- -- pass
+ -- pass
-- else
-- return false, "unexpected packet received: "..tostring(packet)
end
+
end
end)
return true
@@ -330,4 +334,4 @@ function client:close( ... )
self.sock = nil
end
-return client
\ No newline at end of file
+return client
diff --git a/lualib/protocol/mqtt/protocol.lua b/lualib/protocol/mqtt/protocol.lua
index 4e3ff76a..acdafffc 100644
--- a/lualib/protocol/mqtt/protocol.lua
+++ b/lualib/protocol/mqtt/protocol.lua
@@ -40,7 +40,7 @@ local band = bit.band
local lshift = bit.lshift
local rshift = bit.rshift
local div = tools.div
-local unpack = unpack or table.unpack
+local unpack = table.unpack
-- Create uint8 value data
diff --git a/lualib/protocol/mssql.lua b/lualib/protocol/mssql.lua
new file mode 100644
index 00000000..c5bdfd14
--- /dev/null
+++ b/lualib/protocol/mssql.lua
@@ -0,0 +1,1050 @@
+--[[
+ LICENSE: BSD
+ Author: CandyMi[https://github.com/candymi]
+]]
+
+local stream = require "stream"
+local tcp = require "internal.TCP"
+
+local crypt = require "crypt"
+local hexencode = crypt.hexencode
+
+local sys = require "sys"
+local now = sys.now
+local new_tab = sys.new_tab
+local hostname = sys.hostname
+
+local null = null
+local type = type
+local error = error
+local tostring = tostring
+local strpack = string.pack
+local strunpack = string.unpack
+
+local strchar = string.char
+local strbyte = string.byte
+
+local fmt = string.format
+local strgsub = string.gsub
+local strsub = string.sub
+
+local toint = math.tointeger
+
+local os_date = os.date
+local os_time = os.time
+
+local tabconcat = table.concat
+
+-- TDS公共头部类型
+
+local PTYPE_QUERY = 0x01 -- 回应包类型
+
+local PTYPE_RESPONSE = 0x04 -- 回应包类型
+
+local PTYPE_LOGIN = 0x10 -- TDS-7.0 登录类型
+
+-- TDS控制TOKEN类型
+
+local ORDER_TOKEN = 0xA9
+
+local ERROR_TOKEN = 0xAA
+
+local INFO_TOKEN = 0xAB
+
+local ACK_TOKEN = 0xAD
+
+local ENVCHANGE_TOKEN = 0xE3
+
+local COLMETADATA_TOKEN = 0x81
+
+local COLMETAROW_TOKEN = 0xD1
+
+local DONE_TOKEN = 0xFD
+
+-- TDS ENVCHANGE 字段表
+local TDS_ENV = {
+ "Database",
+ "Language",
+ "Character_set",
+ "Packet_size",
+ "Unicode_sorting_local_id",
+ "Unicode_sorting_comparison_flags",
+ "SQL_Collation",
+ "Begin_Transaction (described in [MSDN-BEGIN])",
+ "Commit_Transaction (described in [MSDN-COMMIT])",
+ "Rollback_Transaction",
+ "Enlist_DTC_Transaction",
+ "Defect_Transaction",
+ "Real_Time_Log_Shipping",
+ "Promote_Transaction",
+ "Transaction_Manager_Address",
+ "Transaction_ended",
+ "Acknowledgement",
+ "BackInfo",
+ "Routing",
+}
+
+-- TDS字符集之间的转换兼容函数
+
+-- 引入libiconv库实现 USC-2LE 与 UTF8 之间的转换
+local ok, iconv = pcall(require, "liconv")
+if not ok then
+ error("MSSQL Driver Must have Install libiconv.")
+end
+local liconv_to, liconv_from = iconv.to, iconv.from
+
+local function iconv_to(s, code)
+ return liconv_to(code, s)
+end
+
+local function iconv_from(s, code)
+ return liconv_from(code, s)
+end
+
+local function TO_UCS2LE (s)
+ return liconv_to("UCS-2LE", s)
+end
+
+local function FROM_UCS2LE(s)
+ return liconv_from("UCS-2LE", s)
+end
+
+--[[
+加密算法原理:
+ 1. 对每个字符同时进行高/低位位移(4位);
+ 2. 将位移后的结果高位与低位进行"或"运算;
+ 3. 再将之后的运算结果异或0xA5(10100101);
+ 4. 最终的结果按位"与"0xFF取范围0~255;
+C 函数原型:
+uint8_t* tds7_crypt_pass(const uint8_t *clear_pass, int len, uint8_t *crypt_pass) {
+ for (int i = 0; i < len; i++)
+ crypt_pass[i] = ((clear_pass[i] << 4) | (clear_pass[i] >> 4)) ^ 0xA5;
+ return crypt_pass;
+}
+--]]
+local function password_encrypt(password)
+ return strgsub(password, ".", function (ch)
+ return strchar(((strbyte(ch) << 4 | strbyte(ch) >> 4) ~ 0xA5 ) & 0xff)
+ end)
+end
+
+-- TDS数据类型(N的位置不固定是因为官方文档命名的问题)
+local TYPE_BITN = 0x68 -- (104) BITN
+local TYPE_BIT = 0x32 -- (50) BIT
+
+local TYPE_INTN = 0x26 -- (38) INTN
+local TYPE_INT1 = 0x30 -- (48) INT1
+local TYPE_INT2 = 0x34 -- (54) INT2
+local TYPE_INT4 = 0x38 -- (56) INT4
+local TYPE_INT8 = 0x7F -- (127) INT8
+
+local TYPE_FLOAT32 = 0x3B -- (59) Float32
+local TYPE_DATETIME = 0x3d -- (59) Float32
+local TYPE_FLOAT64 = 0x3E -- (62) Float64
+local TYPE_DECIMAL = 0x6A -- (106) Decimal
+local TYPE_NUMERIC = 0x6C -- (108) Numeric
+local TYPE_FLOATN = 0x6D -- (109) float32/64
+
+local TYPE_MONEY8 = 0x3C -- (60) Money
+local TYPE_MONEY4 = 0x7A -- (122) SmallMoney
+local TYPE_MONEYN = 0x6E -- (110) NMoney
+
+local TYPE_BIGBIN = 0xAD -- (173) NBINARY
+local TYPE_CHAR = 0xAF -- (175) Char
+local TYPE_VARCHAR = 0xA7 -- (167) VarChar
+local TYPE_NVARCHAR = 0xE7 -- (231) NVarChar
+local TYPE_NCHAR = 0xEF -- (239) NChar
+local TYPE_TEXT = 0x23 -- (35) TEXT
+local TYPE_NTEXT = 0x63 -- (99) NTEXT
+
+local TYPE_DATETIMEN = 0x6F -- (111) DATETIME
+
+local TYPE_GEO = 0x22 -- (34) GEO
+
+local TYPE_GUID = 0x24 -- (36) GUID
+
+local TYPE_HIER = 0xA5 -- (165) Hierarchyid
+
+-- TDS Field转换方法
+local FTYPE_TAB = {}
+
+FTYPE_TAB[TYPE_HIER] = function (packet, pos)
+ return pos + 2
+end
+
+FTYPE_TAB[TYPE_GUID] = function (packet, pos)
+ return pos + 1
+end
+
+FTYPE_TAB[TYPE_GEO] = function (packet, pos)
+ local large_type_size, table_name_len
+ large_type_size, table_name_len, pos = strunpack("I2", packet, pos + 8)),
+ fmt("%02X", strunpack(">I6", packet, pos + 10))
+ }, "-"), pos + 16
+end
+
+RTYPE_TAB[TYPE_BIT] = function (packet, pos)
+ local value
+ value, pos = strunpack("BBI2I2BB", OP_CODE, STATUS, LENGTH, CHANNEL, PACKNO, WINDOW)
+end
+
+local function tds_unpack_header(packet)
+ return strunpack(">BBI2I2BB", packet)
+end
+
+local function tds_login7( self )
+
+ local HOSTNAME = hostname()
+ -- print(HOSTNAME, #HOSTNAME)
+
+ local APP_NAME = "cfadmin"
+ -- print(APP_NAME, #APP_NAME)
+
+ local LOCALE = "us_english"
+ -- print(LOCALE, #LOCALE)
+
+ local SERVER_NAME = self.host
+ local DATABASE = self.database
+ local USERNAME = self.username
+ local PASSWORD = self.password
+
+ local msg = {
+ -- LOGIN 7 默认头部信息
+ strpack("BBI2", 7, 1, 1), -- Client Version (7.1.1)
+ strpack(" 0 then
+ field_name = FROM_UCS2LE(strsub(packet, pos, pos + field_length * 2 - 1))
+ pos = pos + field_length * 2
+ end
+ fields[#fields+1] = { field_name = field_name, field_type = field_type, precision = precision, scale = scale }
+ end
+ return fields, pos
+end
+
+local function tds_get_row_data (packet, pos, fields)
+ -- var_dump(fields)
+ local rows = new_tab(#fields, 0)
+ local value
+ for index = 1, #fields do
+ local field = fields[index]
+ local f = RTYPE_TAB[field.field_type]
+ -- print(pos)
+ if type(f) == 'function' then
+ value, pos = f(packet, pos, field.precision, field.scale)
+ else
+ error("Error: Unknown data type [" .. field.field_type .. "] in " .. index)
+ end
+ -- print(field.field_type, value, pos)
+ rows[#rows+1] = value
+ end
+ return rows, pos
+end
+
+local function tds_done_to_tab(tab, status, operation, row_count)
+ -- 这个值应该被忽略
+ tab["DONE_OPERATION"] = operation
+ -- 是否是最终的数据包
+ tab["DONE_FINAL"] = status & 0x01 == 0x00 and true or false
+ -- 是否还有其他数据包
+ tab["DONE_MORE"] = status & 0x01 == 0x01 and true or false
+ -- 是否是一个错误数据包
+ tab["DONE_ERROR"] = status & 0x01 == 0x01 and true or false
+ -- 是否正在处理一个事务
+ tab["DONE_TRANSACTION"] = status & 0x04 == 0x04 and true or false
+ -- TODO
+ tab["DONE_COUNT"] = status & 0x10 == 0x10 and row_count or 0
+ -- TODO
+ tab["DONE_ATTN"] = status & 0x20 == 0x20 and true or false
+end
+
+local function tds_get_done (packet, pos)
+ return strunpack("= #tds_data then
+ self:write(tds_pack_header(PTYPE_QUERY, 0x01, #tds_data + 8, 0, 1, 0) .. tds_data)
+ else
+ local fin = 0x00
+ while 1 do
+ local body = strsub(tds_data, 1, self.max_packet_size)
+ if #tds_data <= self.max_packet_size then
+ fin = 0x01
+ end
+ self:write(tds_pack_header(PTYPE_QUERY, fin, #body + 8, 0, 1, 0) .. body)
+ if fin == 0x01 then
+ break
+ end
+ tds_data = strsub(tds_data, self.max_packet_size + 1, -1)
+ end
+ end
+ return tds_read_response(self)
+end
+
+local class = require "class"
+
+local mssql = class("mssql")
+
+function mssql:ctor(opt)
+ self.sock = stream(tcp())
+ self.host = opt.host or "localhost"
+ self.port = opt.port or 1433
+ self.unixdomain = opt.unixdomain
+ self.TSQL = opt.TSQL == 1 and 1 or 0
+ self.max_packet_size = opt.max_packet_size or 10240
+ self.database = opt.database or "master"
+ self.username = opt.username or "sa"
+ self.password = opt.password
+ -- self.state = true
+end
+
+function mssql:read( bytes )
+ return self.sock:readbytes(bytes)
+end
+
+function mssql:write(data)
+ return self.sock:send(data)
+end
+
+function mssql:connect( ... )
+ if not self.sock then
+ return nil, "Connection failed: please recreate the socket object."
+ end
+
+ if self.unixdomain then
+ if not self.sock:connectx(self.unixdomain) then
+ return nil, "MSSQL Server [" .. tostring(self.unixdomain) .. "] Connect failed."
+ end
+ elseif self.host and self.port then
+ if not self.sock:connect(self.host, self.port) then
+ return nil, "MSSQL Server TCP Connect failed."
+ end
+ else
+ return nil, "MSSQL Server driver Invalid Configure."
+ end
+
+ -- 发送TDS-7.0登录协议
+ self:write(tds_login7(self))
+
+ local packet = tds_read_head(self)
+ if not packet then
+ return nil, "1. After sending the LOGIN data, the server disconnected."
+ end
+
+ local OPCODE, STATUS, LENGTH, CHANNEL, PACKNO, WINDOW = tds_unpack_header(packet)
+ if OPCODE ~= PTYPE_RESPONSE then
+ return nil, "A protocol type not supported by TDS-7.0 was received."
+ end
+
+ local packet = tds_read_body(self, LENGTH)
+ if not packet then
+ return nil, "2. After sending the LOGIN data, the server disconnected."
+ end
+
+ local sever = new_tab(0, 6)
+ local pos
+ while 1 do
+ local token_type
+ token_type, pos = strunpack(" 0 then
+ if env_type ~= 0x07 then
+ new_value = FROM_UCS2LE(strsub(packet, pos, pos + new_len * 2 - 1))
+ pos = pos + new_len * 2
+ else
+ collate_codepage, collate_flags, collate_charset_id, pos = strunpack(" 0 then
+ old_value = FROM_UCS2LE(strsub(packet, pos, pos + old_len * 2 - 1))
+ pos = pos + old_len * 2
+ end
+ sever[type_name] = {
+ new_value = new_value,
+ old_value = old_value,
+ collate_codepage = collate_codepage,
+ collate_flags = collate_flags,
+ collate_charset_id = collate_charset_id
+ }
+ -- return sever
+ elseif token_type == INFO_TOKEN then
+ local info
+ info, pos = tds_get_errorinfo(packet, pos)
+ elseif token_type == ACK_TOKEN then
+ local len, interface, tds_version, server_name_len, server_name, server_version_max1, server_version_max2, server_version_min
+ len, pos = strunpack(" self._max_packet_size then
- return nil, nil, "packet size too big: " .. len
- end
-
- local num = strbyte(data, pos)
-
- --print("recv packet: packet no: ", num)
-
- self.packet_no = num
-
- data, err = sock:recv(len)
-
- --print("receive returned")
-
- if not data then
- self.state = nil
- return nil, nil, "failed to read packet content: "..(err or "nil")
- end
-
- local field_count = strbyte(data, 1)
+local function get_lenenc_str (packet, pos)
+ local bit, len
+ bit, pos = strunpack("= 0 and first <= 250 then
- return first, pos + 1
- end
-
- if first == 251 then
- return null, pos + 1
- end
-
- if first == 252 then
- pos = pos + 1
- return _get_byte2(data, pos)
- end
-
- if first == 253 then
- pos = pos + 1
- return _get_byte3(data, pos)
- end
-
- if first == 254 then
- pos = pos + 1
- return _get_byte8(data, pos)
- end
-
+ local first = strbyte(data, pos)
+ if not first then
+ return nil, pos
+ end
+ if first >= 0 and first <= 250 then
+ return first, pos + 1
+ end
+ if first == 251 then
return nil, pos + 1
+ end
+ if first == 252 then
+ pos = pos + 1
+ return strunpack(" 2 then
+ null_fields[field_index - 2] = byte & (1 << j) ~= 0 and true or false
+ end
+ field_index = field_index + 1
+ end
+ end
+
+ local parser
+ local row = new_tab(0, ncols)
+ for i = 1, ncols do
+ local col = cols[i]
+ -- var_dump(col)
+ if not null_fields[i] then
+ parser = binary_parser[col.field_type]
+ if not parser then
+ error("error! field key[" .. col.field_name .."] unsupported type " .. col.field_type)
+ end
+ row[col.field_name], pos = parser(data, pos, col.is_signed)
+ else
+ row[col.field_name] = null
end
-
- return errno, sub(packet, pos), sqlstate
+ end
+ return row
end
-
-local function _parse_result_set_header_packet(packet)
- local field_count, pos = _from_length_coded_bin(packet, 1)
-
- local extra
- extra = _from_length_coded_bin(packet, pos)
-
- return field_count, extra
+local function get_eof_packet (packet)
+ local pos = 1
+ local warning_count, status_flags
+ warning_count, pos = strunpack(" pos then
+ message, pos = strunpack("s1", packet, pos)
+ end
+ return {
+ auto_commit = server_status & 0x02 == 0x02 and true or false, transaction = server_status & 0x01 == 0x01 and true or false,
+ last_insertid = last_insertid, affected_rows = affected_rows, server_warnings = server_warnings, message = message
+ }, server_status & SERVER_MORE_RESULTS == SERVER_MORE_RESULTS
end
-
-local function _recv_field_packet(self)
- local packet, typ, err = _recv_packet(self)
+local function read_response (self, results)
+ local packet = read_packet(self)
+ if not packet then
+ self.state = nil
+ return nil, "1. mysql server closed when client sended query packet."
+ end
+ local status = strbyte(packet, 1)
+ if status == RESP_ERROR then
+ return nil, get_mysql_error_packet(packet:sub(2))
+ end
+ if status == RESP_OK then
+ local tab, again = get_ok(packet:sub(2), #packet - 1)
+ if again then -- 如果是`多结果集的数据
+ if type(results) == 'table' then
+ results[#results+1] = tab
+ return read_response(self, results)
+ end
+ return read_response(self, { tab })
+ end
+ if type(results) == 'table' then
+ results[#results+1] = tab
+ tab = results
+ end
+ return tab
+ end
+
+ local fields = new_tab(status, 0)
+ for index = 1, status do
+ local field, err = get_field(self)
+ if not field then
+ self.state = nil
+ return nil, err
+ end
+ fields[index] = field
+ end
+
+ local again = false
+ local len = read_head(self)
+ if not len then
+ self.state = nil
+ return nil, "2. mysql server closed when client sended query packet."
+ end
+ local status, err = read_status(self)
+ if not status then
+ self.state = nil
+ return nil, err
+ end
+ local sever = get_eof(self, len - 1)
+ if sever.status_flags & SERVER_MORE_RESULTS == SERVER_MORE_RESULTS then
+ again = true
+ end
+
+ local rows = new_tab(32, 0)
+ while 1 do
+ packet, err = read_packet(self)
if not packet then
- return nil, err
- end
-
- if typ == "ERR" then
- local errno, msg, sqlstate = _parse_err_packet(packet)
- return nil, msg, errno, sqlstate
+ self.state = nil
+ return nil, err
+ end
+ local b = strbyte(packet, 1)
+ if b == RESP_EOF and #packet < 9 then
+ local sever = get_eof_packet(packet)
+ if sever.status_flags & SERVER_MORE_RESULTS == SERVER_MORE_RESULTS then
+ again = true
+ end
+ break
+ end
+ if b == RESP_ERROR then
+ return nil, get_mysql_error_packet(packet:sub(2))
+ end
+ rows[#rows+1] = get_rows(packet, #fields)
+ end
+
+ local result = new_tab(#rows, 0)
+ for _, row in ipairs(rows) do
+ local tab = new_tab(0, #fields)
+ for index, item in ipairs(row) do
+ local field = fields[index]
+ local call = converts[field.field_type]
+ if not call then
+ -- print("not call")
+ tab[field.field_name] = item
+ else
+ -- print(field.field_type, field.field_name, item, call(item), item == null)
+ tab[field.field_name] = call(item)
+ end
+ end
+ result[#result+1] = tab
+ end
+
+ if again then
+ if type(results) == 'table' and #results > 0 then
+ results[#results+1] = result
+ return read_response(self, results)
+ else
+ return read_response(self, { result })
end
+ end
- if typ ~= 'DATA' then
- return nil, "bad field packet type: " .. typ
- end
+ if results then
+ results[#results+1] = result
+ end
+ return results or result
+end
- return _parse_field_packet(packet)
+local function send_packet (self, request)
+ self.packet_no = self.packet_no + 1
+ return sock_write(self, strpack(" 0 then
+ for _ = 1, num do
+ local len = read_head(self)
+ if not len then
+ return false
+ end
+ sock_read(self, len)
+ end
+ local len = read_head(self)
+ if not sock_read(self, len) then
+ return false
+ end
+ end
+ return true
+end
-function MySQL.ctor(self)
+local function read_prepare_response(self)
+ local packet = read_packet(self)
+ if not packet then
+ self.state = nil
+ return nil, "1. mysql server closed when client sended query packet."
+ end
+ -- 预编译status只有OK或ERROR
+ local status = strbyte(packet, 1)
+ if status == RESP_ERROR then
+ return nil, get_mysql_error_packet(packet:sub(2))
+ end
+ if status ~= RESP_OK then
+ return nil, "2. Invalid mysql prepare protocol."
+ end
+ local info = {}
+ info.sid, info.fields, info.params, info.warnings = strunpack(" 0 then
+ local field_index = 1
+ local null_map = ""
+ local null_count = (argn + 7) // 8
+ -- null-bitmap 必须检查正确.
+ for _ = 1, null_count do
+ local nbyte = 0
+ for offset = 0, 7 do
+ if field_index <= argn then
+ local v = args[field_index]
+ nbyte = nbyte | (((v == nil or type(v) == 'userdata') and 1 or 0) << offset)
+ end
+ field_index = field_index + 1
+ end
+ null_map = null_map .. strchar(nbyte)
+ end
+ local tb_idx, vb_idx = 1, 1
+ local types_buf, values_buf = new_tab(16, 0), new_tab(16, 0)
+ for i = 1, argn, 1 do
+ local v = args[i]
+ local f = fmap[type(v)]
+ if not f then
+ error("invalid parameter type :" .. type(v))
+ end
+ types_buf[tb_idx], values_buf[vb_idx] = f(v)
+ tb_idx, vb_idx = tb_idx + 1, vb_idx + 1
+ end
+ sql = concat{sql, null_map, '\x01', concat(types_buf), concat(values_buf)}
+ end
+ send_packet(self, sql)
+ return read_execute_reponse(self)
+end
- if typ == 'EOF' then
- return nil, "old pre-4.1 authentication protocol not supported"
- end
+local function mysql_prepare(self, stmt)
+ send_packet(self, strpack("zzzzzzz", msg)
+ return strsub(severity, 2), strsub(text, 2), strsub(code, 2), strsub(message, 2), strsub(file, 2), strsub(line, 2), strsub(routine, 2)
+end
+
+local function get_error_message_fmt(msg)
+ local _, etype, code, message = get_error_message(msg)
+ return nil, fmt("[%s] : {'%s', '%s'}", code, etype, message)
+end
+
+local function get_error_message_tab(msg)
+ local severity, text, code, message, file, line, routine = string.unpack(">zzzzzzz", msg)
+ return {
+ severity = severity,
+ text = text,
+ code = code,
+ message = message,
+ file = file,
+ line = line,
+ routine = routine
+ }
+end
+
+local function read_opcode_and_len (self)
+ local opstr = self:read(1)
+ if not opstr then
+ return nil, "client read: server close this session."
+ end
+ local opcode = RESPONSES[strbyte(opstr)]
+ local len_byte, err = self:read(4)
+ if not len_byte then
+ return nil, "client read: server close this session."
+ end
+ local len = strunpack(">I4", len_byte)
+ if not len then
+ return nil, "An unrecognized message type was received."
+ end
+ return opcode, len
+end
+
+local function read_head (self)
+ local opcode, len = read_opcode_and_len(self)
+ return opcode, len, self:read(4)
+end
+
+local function get_query_error_msg (self, data)
+ local _, etype, code, message = get_error_message(data)
+ local msg = fmt("[%s] : {'%s', '%s'}", code, etype, message)
+ local opcode, len = read_opcode_and_len(self)
+ if opcode ~= RESP_READY then
+ return nil, len
+ end
+ local _ = self:read(len - 4)
+ return nil, msg
+end
+
+local function read_column_data (self, data_len)
+ local row_data = self:read(data_len)
+ if not row_data then
+ return nil, "client read: server close this session. "
+ end
+ local len, index = strunpack(">I2", row_data)
+ local columns = new_tab(len, 0)
+ for i = 1, len do
+ local column_name, pos = strunpack(">z", row_data, index)
+ -- print(column_name, pos)
+ local column_table_oid, pos = strunpack(">I4", row_data, pos)
+ -- print(column_table_oid, pos)
+ local column_index, pos = strunpack(">I2", row_data, pos)
+ -- print(column_index, pos)
+ local column_type_oid, pos = strunpack(">I4", row_data, pos)
+ -- print(column_type_oid, pos)
+ local column_length, pos = strunpack(">I2", row_data, pos)
+ -- print(column_length, pos)
+ local column_type_modifier, pos = strunpack(">i4", row_data, pos)
+ -- print(column_type_modifier, pos)
+ local column_format, pos = strunpack(">I2", row_data, pos)
+ -- print(column_format, pos)
+ index = pos
+ columns[#columns+1] = {
+ column_name = column_name,
+ column_type_oid = column_type_oid,
+ -- column_index = column_index,
+ -- column_length = column_length,
+ -- column_format = column_format,
+ -- column_table_oid = column_table_oid,
+ -- column_type_modifier = column_type_modifier,
+ }
+ end
+ return columns
+end
+
+local function read_row_data (self)
+ local opcode, len = read_opcode_and_len(self)
+ if not opcode then
+ return nil, "server close this session."
+ end
+ if opcode == RESP_CMD_COMPLETION then
+ return opcode, len
+ end
+ local row_data = self:read(len - 4)
+ if not row_data then
+ return nil, "server close this session."
+ end
+ local index = 3
+ local count = strunpack(">I2", row_data)
+ local row = new_tab(count, 0)
+ for i = 1, count do
+ local raw_len, pos = strunpack(">i4", row_data, index)
+ -- print(raw_len)
+ if raw_len > -1 then
+ row[#row + 1] = row_data:sub(pos, pos + raw_len - 1)
+ index = pos + raw_len
+ else
+ row[#row + 1] = null
+ index = pos
+ end
+ end
+ return row
+end
+
+local function read_response (self)
+ local results = {}
+ while 1 do
+ local opcode, len = read_opcode_and_len(self)
+ if not opcode then
+ self.state = "closed"
+ return nil, "1. server close this session."
+ end
+ if opcode == RESP_ERROR then
+ return get_query_error_msg(self, self:read(len - 4):sub(5))
+ end
+ local result
+ if opcode == RESP_STATUS then
+ local kv = self:read(len - 4)
+ if not kv then
+ self.state = "closed"
+ return nil, "2. server close this session."
+ end
+ local k, v = strunpack("zz", kv)
+ if not result then
+ result = { ok = true, [k] = v }
+ else
+ result[k] = v
+ end
+ result['ok'] = true
+ result['action'] = "SET"
+ result['status'] = "Idle"
+ results[#results + 1] = result
+ local opcode, len = read_opcode_and_len(self)
+ if not opcode then
+ self.state = "closed"
+ return nil, "3. server close this session."
+ end
+ if opcode == RESP_CMD_COMPLETION then
+ local _ = self:read(len - 4)
+ end
+ elseif opcode == RESP_CMD_COMPLETION then
+ local tab = new_tab(3, 0)
+ local content = self:read(len - 4)
+ if not content then
+ self.state = "closed"
+ return nil, "4. server close this session."
+ end
+ for v in strgmatch(content, "[^ \x00]+") do
+ tab[#tab+1] = v
+ end
+ if not result then
+ result = new_tab(0, 5)
+ end
+ local action = tab[1]
+ result['ok'] = true
+ result['status'] = "Idle"
+ result['action'] = action
+ if action == "INSERT" then
+ result['oid'] = toint(tab[2])
+ result['affected_rows'] = toint(tab[3])
+ elseif action == "UPDATE" or action == "DELETE" then
+ result['affected_rows'] = toint(tab[2])
+ else
+ result["rows"] = toint(tab[2])
+ end
+ results[#results + 1] = result
+ -- var_dump(results)
+ elseif opcode == RESP_READY then
+ local v = self:read(len - 4)
+ if not v then
+ self.state = "closed"
+ return nil, "5. server close this session."
+ end
+ -- if v == "T" then
+ -- results[#results].transaction = true
+ -- else
+ -- results[#results].transaction = false
+ -- end
+ break
+ elseif opcode == RESP_COLUMN then
+ local columns, err = read_column_data(self, len - 4)
+ if not columns then
+ self.state = "closed"
+ return nil, err
+ end
+ -- var_dump(columns)
+ local row, len
+ local rows = new_tab(128, 0)
+ while 1 do
+ row, len = read_row_data(self)
+ if type(row) == 'table' then
+ rows[#rows + 1] = row
+ elseif type(row) == 'number' then
+ break
+ else
+ self.state = "closed"
+ return nil, "6. server close this session."
+ end
+ end
+ -- var_dump(rows)
+ result = new_tab(#rows, 0)
+ for _, row in ipairs(rows) do
+ local tab = {}
+ for index, column in ipairs(columns) do
+ tab[column.column_name] = convert(column.column_type_oid, row[index])
+ end
+ result[#result + 1] = tab
+ end
+ results[#results+1] = result
+ if row == RESP_CMD_COMPLETION then
+ local v = self:read(len - 4)
+ if not v then
+ self.state = "closed"
+ return nil, "7. server close this session."
+ end
+ end
+ end
+ end
+ return #results == 1 and results[1] or results
+end
+
+-- AUTH_MD5
+local function auth_md5(auth_user, auth_password, auth_salt)
+ return "md5" .. md5(md5(auth_password .. auth_user, true) .. auth_salt, true)
+end
+
+-- 设置客户端连接参数
+local function set_conn_param(key, value)
+ return strpack("zz", key, value)
+end
+
+local class = require "class"
+
+local pgsql = class("pgsql")
+
+function pgsql:ctor(opt)
+ self.sock = tcp:new()
+ self.host = opt.host or "localhost"
+ self.port = opt.port or 5432
+ self.unixdomain = opt.unixdomain
+ self.database = opt.database or "postgres"
+ self.username = opt.username or "postgres"
+ self.password = opt.password or "postgres"
+ self.charset = opt.charset or "UTF8"
+ self.application_name = opt.application_name or "cfadmin"
+ -- self.state = "connected"
+end
+
+function pgsql:read(bytes)
+ return self.sock:readbytes(bytes)
+end
+
+function pgsql:write(data)
+ return self.sock:send(data)
+end
+
+function pgsql:startup ()
+ local connect_params = tconcat {
+ strpack(">I2I2", 3, 0), -- 使用3.0交互协议
+ set_conn_param("user", self.username), -- 设置客户端登录名
+ set_conn_param("database", self.database), -- 设置客户端数据库
+ set_conn_param("client_encoding", self.charset), -- 设置客户端字符集
+ set_conn_param("application_name", self.application_name), -- 设置客户端应用名
+ "\x00",
+ }
+ return strpack(">I4", #connect_params + 4) .. connect_params
+end
+
+function pgsql:authmd5 (data)
+ return strpack(">BI4z", strbyte("p"), 40, auth_md5(self.username, self.password, data))
+end
+
+function pgsql:connect()
+
+ if self.unixdomain then
+ if not self.sock:connect_ex(self.unixdomain or "") then
+ return nil, "PGSQL Server [" .. tostring(self.unixdomain) .. "] Connect failed."
+ end
+ elseif self.host and self.port then
+ if not self.sock:connect(self.host, self.port) then
+ return nil, "PGSQL Server TCP Connect failed."
+ end
+ else
+ return nil, "PGSQL Server driver Invalid Configure."
+ end
+
+ -- Socket Stream Wrapper.
+ self.sock = stream(self.sock)
+
+ -- 发送启动协议
+ self:write(self:startup())
+
+ local opcode, len, auth_type = read_head(self)
+ if opcode ~= RESP_OK or not auth_type then
+ return nil, "1. Malformed response packets."
+ end
+ if opcode == RESP_ERROR then
+ return get_error_message_fmt(auth_type .. self:read(len - 8) )
+ end
+
+ auth_type = strunpack(">I4", auth_type)
+ if auth_type == RESP_AUTHMD5 then
+ local status
+ self:write(self:authmd5(self:read(len - 8)))
+ opcode, len, status = read_head(self)
+ if not opcode or not status then
+ return nil, "2. Malformed response packets."
+ end
+ if opcode == RESP_ERROR then
+ return get_error_message_fmt(status .. self:read(len - 8) )
+ end
+ end
+
+ -- 获取服务器配置信息
+ local server = new_tab(0, 16)
+ while 1 do
+ opcode, len = read_opcode_and_len(self)
+ if opcode == RESP_STATUS then
+ local k, v = strunpack("zz", self:read(len - 4))
+ server[k] = v == 'on' and true or v
+ elseif opcode == RESP_STATUS_END then
+ server["pid"] = strunpack(">I4", self:read(4))
+ server["key"] = strunpack(">I4", self:read(4))
+ elseif opcode == RESP_READY then
+ server["status"] = "Idle"
+ self:read(len - 4) -- 读取并丢弃无用的数据
+ break
+ elseif opcode == RESP_ERROR then
+ return get_error_message_fmt(self:read(len - 4) )
+ else
+ return nil, "3. Malformed response packets."
+ end
+ end
+
+ self.state = "connected"
+ self.server = server
+ return server
+end
+
+function pgsql:query (sql)
+ if type(sql) ~= 'string' or sql == '' then
+ return nil, "Invalid SQL."
+ end
+ self:write(strpack(">BI4z", OP_QUERY, #sql + 5, sql))
+ return read_response(self)
+end
+
+local escape_map = {
+ ['\0'] = "\\0",
+ ['\b'] = "\\b",
+ ['\n'] = "\\n",
+ ['\r'] = "\\r",
+ ['\t'] = "\\t",
+ ['\26'] = "\\Z",
+ ['\\'] = "\\\\",
+ ["'"] = "\\'",
+ ['"'] = '\\"',
+}
+
+function pgsql.quote_to_str (sql)
+ return fmt("%s", string.gsub(sql, "[\0\b\n\r\t\26\\\'\"]", escape_map))
+end
+
+function pgsql:set_timeout(timeout)
+ if self.sock and tonumber(timeout) then
+ self.sock:timeout(timeout)
+ end
+end
+
+function pgsql:close()
+ if self.state == "connected" then
+ self.state = "closed"
+ self:write(strpack(">BI4", OP_TERMINATE, 4))
+ end
+ if self.sock then
+ self.sock:close()
+ self.sock = nil
+ end
+end
+
+return pgsql
\ No newline at end of file
diff --git a/lualib/protocol/redis.lua b/lualib/protocol/redis.lua
index 132a9b49..34118641 100644
--- a/lualib/protocol/redis.lua
+++ b/lualib/protocol/redis.lua
@@ -1,212 +1,258 @@
-local log = require "log"
+local log = require "logging"
local class = require "class"
local Co = require "internal.Co"
local tcp = require "internal.TCP"
-local table = table
-local concat = table.concat
+local stream = require "stream"
+local tcp_send = stream.send
+local tcp_readline = stream.readline
+local tcp_readbytes = stream.readbytes
+
+local new_tab = require "sys".new_tab
+
+local Log = log:new({ dump = true, path = 'protocol-redis' })
+
+local co_spawn = Co.spawn
-local co_spwan = Co.spwan
+local type = type
+local pcall = pcall
+local ipairs = ipairs
+local assert = assert
+local tonumber = tonumber
+local tostring = tostring
+
+local insert = table.insert
+local concat = table.concat
+local unpack = table.unpack
local sub = string.sub
-local string = string
-local match = string.match
-local find = string.find
local byte = string.byte
-local upper = string.upper
+local toint = math.tointeger
local CRLF = '\x0d\x0a'
local redcmd = {}
local function read_response(sock)
- local result = ""
- while 1 do
- local data = sock:recv(1)
- if not data then
- return nil, 'server close!!'
- end
- result = result .. data
- if find(result, CRLF) then
- break
- end
- end
- local firstchar = byte(result)
- return redcmd[firstchar](sock, sub(result, 2))
+ local result = tcp_readline(sock, CRLF)
+ if not result then
+ sock.state = false
+ return nil, 'server close!!'
+ end
+ -- 断言redis 协议是否支持用于快速排错
+ return assert(redcmd[byte(result)], "Invalid protocol command : " .. sub(result, 1, 1))(sock, sub(result, 2))
+end
+
+local function sock_readbytes(sock, bytes)
+ return tcp_readbytes(sock, bytes)
end
redcmd[36] = function(sock, data) -- '$'
- local bytes = tonumber(data)
- if bytes < 0 then
- return true, nil
- end
- local firstline = sock:recv(bytes + 2)
- return true, sub(firstline, 1, -3)
+ local bytes = tonumber(data)
+ if bytes < 0 then
+ return true, nil
+ end
+ local firstline = sock_readbytes(sock, bytes + 2)
+ return true, sub(firstline, 1, -3)
end
redcmd[43] = function(sock, data) -- '+'
- return true, match(data, '(.+)'..CRLF)
+ return true, sub(data, 1, -3)
end
redcmd[45] = function(sock, data) -- '-'
- return false, data
+ return false, sub(data, 1, -3)
end
redcmd[58] = function(sock, data) -- ':'
- -- todo: return string later
- return true, tonumber(data)
+ -- todo: return string later
+ return true, tonumber(data)
end
redcmd[42] = function(sock, data) -- '*'
- local n = tonumber(data)
- if n < 0 then
- return true, nil
- end
- local bulk = {}
- local noerr = true
- for i = 1,n do
- local ok, v = read_response(sock)
- if not ok then
- noerr = false
- end
- bulk[i] = v
- end
- return noerr, bulk
+ local n = tonumber(data)
+ if n < 0 then
+ return true, nil
+ end
+ local bulk = new_tab(n, 0)
+ local noerr = true
+ for i = 1, n do
+ local ok, v = read_response(sock)
+ if not ok then
+ noerr = false
+ end
+ bulk[i] = v
+ end
+ return noerr, bulk
end
-- 格式化命令为redis protocol
local function CMD(...)
- local tab = {...}
- local lines = { "*"..#tab}
- for index = 1, #tab do
- lines[#lines+1] = "$"..#tostring(tab[index])
- lines[#lines+1] = tab[index]
- if index == #tab then
- lines[#lines+1] = ""
- end
- end
- return concat(lines, CRLF)
+ local tab = {...}
+ local lines = new_tab(#tab, 0)
+ lines[#lines+1] = "*"..#tab
+ for index = 1, #tab do
+ lines[#lines+1] = "$"..#tostring(tab[index])
+ lines[#lines+1] = tab[index]
+ if index == #tab then
+ lines[#lines+1] = ""
+ end
+ end
+ return concat(lines, CRLF)
end
local function read_boolean(sock)
- local ok, result = read_response(sock)
- if ok then
- return ok, result ~= 0 or result == "OK"
- end
- return ok, result
+ local ok, result = read_response(sock)
+ if ok then
+ return ok, result ~= 0 or result == "OK"
+ end
+ return ok, result
end
local function redis_login(sock, auth, db)
- if auth then
- sock:send(CMD("AUTH", auth))
- local ok, err = read_response(sock)
- if not ok then
- return nil, err
- end
- end
- if db then
- sock:send(CMD("SELECT", db))
- local ok, err = read_response(sock)
- if not ok then
- return nil, err
- end
- end
- return true
+ if type(auth) == 'string' and auth ~= '' then
+ tcp_send(sock, CMD("AUTH", auth))
+ local ok, err = read_response(sock)
+ if not ok then
+ return nil, err
+ end
+ end
+ if toint(db) and toint(db) >= 0 then
+ tcp_send(sock, CMD("SELECT", db))
+ local ok, err = read_response(sock)
+ if not ok then
+ return nil, err
+ end
+ end
+ sock.state = true
+ return true
end
local redis = class("redis")
function redis:ctor(opt)
- self.sock = tcp:new()
- self.host = opt.host
- self.port = opt.port
- self.db = opt.db
- self.auth = opt.auth
+ self.sock = tcp:new()
+ self.host = opt.host
+ self.port = opt.port
+ self.unixdomain = opt.unixdomain
+ self.auth = opt.auth
+ self.db = opt.db
+end
+
+function redis:isconnected()
+ return self.sock and self.sock.state or false
end
function redis:connect()
- local sock = self.sock
- if not sock then
- return nil, "Can't Create redis Socket"
- end
- local ok, err = sock:connect(self.host, self.port or 6379)
- if not ok then
- return nil, "redis connect error: please check network"
- end
- local ok, err = redis_login(sock, self.auth, self.db)
- if not ok then
- return nil, "redis login error:"..(err or 'close')
- end
- return true
+ -- 尝试多种连接渠道
+ if not self.sock:connect_ex(self.unixdomain or "") and not self.sock:connect(self.host, toint(self.port) or 6379) then
+ return nil, "redis network connect failed."
+ end
+ -- Socket Stream Wrapper.
+ self.sock = stream(self.sock)
+ -- 登录状态检查
+ local ok, err = redis_login(self.sock, self.auth, self.db)
+ if not ok then
+ return nil, "redis login error:" .. (err or 'close')
+ end
+ return true
+end
+
+function redis:set_timeout(timeout)
+ self.sock._timeout = timeout
+ return self
end
-- 订阅
function redis:psubscribe(pattern, func)
- local sock = self.sock
- sock:send(CMD("PSUBSCRIBE", pattern))
- local ok, msg = read_response(sock)
- if not ok or not msg[2] then
- return nil, "PSUBSCRIBE error: 订阅"..tostring(pattern).."失败."
- end
- co_spwan(function ( ... )
- while 1 do
- local ok, msg = read_response(sock)
- if not ok or not msg or not self.sock then
- local ok, err = pcall(func, nil)
- if not ok then
- log.error(err)
- end
- return
- end
- local data = {type = msg[1], source = msg[2], pattern = pattern, payload = msg[3]}
- if #msg > 3 then
- data = {type = msg[1], source = msg[3], pattern = pattern, payload = msg[4]}
- end
- local ok, err = pcall(func, data)
- if not ok then
- return log.error(err)
- end
- end
- end)
- return ok, msg
+ local sock = self.sock
+ tcp_send(sock, CMD("PSUBSCRIBE", pattern))
+ local ok, msg = read_response(sock)
+ if not ok or not msg[2] then
+ return nil, "PSUBSCRIBE error: 订阅"..tostring(pattern).."失败."
+ end
+ co_spawn(function ()
+ while 1 do
+ local ok, msg = read_response(sock)
+ if not ok or not msg or not self.sock then
+ local ok, err = pcall(func, nil)
+ if not ok then
+ Log:ERROR(err)
+ end
+ return
+ end
+ local data = {type = msg[1], source = msg[2], pattern = pattern, payload = msg[3]}
+ if #msg > 3 then
+ data = {type = msg[1], source = msg[3], pattern = pattern, payload = msg[4]}
+ end
+ local ok, err = pcall(func, data)
+ if not ok then
+ return Log:ERROR(err)
+ end
+ end
+ end)
+ return ok, msg
end
-- 订阅
function redis:subscribe(pattern, func)
- return self:psubscribe(pattern, func)
+ return self:psubscribe(pattern, func)
end
-- 发布
function redis:publish(pattern, data)
- local sock = self.sock
- sock:send(CMD("PUBLISH", pattern, data))
- return read_response(sock)
+ local sock = self.sock
+ tcp_send(sock, CMD("PUBLISH", pattern, data))
+ return read_response(sock)
end
-- 查询键是否存在
function redis:exists(key)
- local sock = self.sock
- sock:send(CMD("EXISTS", key))
- return read_boolean(sock)
+ local sock = self.sock
+ tcp_send(sock, CMD("EXISTS", key))
+ return read_boolean(sock)
end
-- 查询元素是否集合成员
function redis:sismember(key, value)
- local sock = self.sock
- sock:send(CMD("SISMEMBER", key, value))
- return read_boolean(sock)
+ local sock = self.sock
+ tcp_send(sock, CMD("SISMEMBER", key, value))
+ return read_boolean(sock)
end
-- 执行命令
function redis:cmd(...)
- local sock = self.sock
- sock:send(CMD(...))
- return read_response(sock)
+ local sock = self.sock
+ tcp_send(sock, CMD(...))
+ return read_response(sock)
+end
+
+-- 管道命令
+function redis:pipeline(opt)
+ local cmds = {}
+ if opt and #opt > 0 then
+ for _, cmd in ipairs(opt) do
+ cmds[#cmds+1] = CMD(unpack(cmd))
+ end
+ end
+ local max_read_times = #cmds
+ if max_read_times > 0 then
+ local sock = self.sock
+ tcp_send(sock, concat(cmds))
+ local rets = new_tab(max_read_times, 0)
+ for index = 1, max_read_times do
+ rets[index] = {read_response(sock)}
+ end
+ return true, rets
+ end
+ return nil
end
function redis:close()
- if self.sock then
- self.sock:close()
- end
+ if self.sock then
+ self.sock.state = false
+ self.sock:close()
+ self.sock = nil
+ end
end
-return redis
\ No newline at end of file
+return redis
diff --git a/lualib/protocol/smtp/init.lua b/lualib/protocol/smtp/init.lua
new file mode 100644
index 00000000..672fa864
--- /dev/null
+++ b/lualib/protocol/smtp/init.lua
@@ -0,0 +1,257 @@
+local class = require "class"
+local tcp = require "internal.TCP"
+
+local crypt = require "crypt"
+local base64encode = crypt.base64encode
+
+local type = type
+local toint = math.tointeger
+local tonumber = tonumber
+local tostring = tostring
+local match = string.match
+local fmt = string.format
+local os_date = os.date
+local concat = table.concat
+
+local MAX_PACKET_SIZE = 4096
+
+local CRLF = '\x0d\x0a'
+
+local function read_packet(str)
+ local str_code, err = match(str, "(%d+) (.+)")
+ local code = tonumber(str_code)
+ if not code then
+ return
+ end
+ return code, err
+end
+
+local function time()
+ return os_date("[%Y/%m/%d %H:%M:%S]")
+end
+
+local smtp = class("smtp")
+
+function smtp:ctor (opt)
+ self.ssl = opt.SSL or opt.ssl
+ self.host = opt.host
+ self.port = opt.port
+ self.to = opt.to
+ self.from = opt.from
+ self.fromName = opt.fromName
+ self.mime = opt.mime
+ self.subject = opt.subject
+ self.content = opt.content
+ self.username = opt.username
+ self.password = opt.password
+ self.sock = tcp:new()
+end
+
+-- 发送握手包
+function smtp:hello_packet ()
+ local code, data, info
+ -- 接收服务端信息
+ data = self:readline(CRLF)
+ if not data then
+ return nil, "SMTP Client Can't connect to server."
+ end
+ code, info = read_packet(data)
+ if not code then
+ return nil, "[HELO ERROR]: Unsupported protocol."
+ end
+ -- 发送HELO命令
+ if not self:sendline("HELO cf_smtp/0.1", CRLF) then
+ return nil, "[HELO ERROR]: Failed to send HELO message."
+ end
+ -- 接收HELO回应
+ data = self:recv(MAX_PACKET_SIZE)
+ if not data then
+ return nil, "SMTP Server Close this session."
+ end
+ code, info = data:sub(1, 3), data:sub(4)
+ if toint(code) ~= 250 and toint(code) ~= 220 then
+ return nil, "[HELO ERROR]: " .. (info or "Invalid Response." )
+ end
+ return true
+end
+
+-- 登录认证
+function smtp:auth_packet ()
+ local code, data, err
+ -- 发送登录认证请求
+ if not self:sendline("AUTH LOGIN", CRLF) then
+ return nil, "AUTH LOGIN ERROR]: SMTP Server Close this session."
+ end
+ data = self:readline(CRLF)
+ if not data then
+ return nil, '[AUTH LOGIN ERROR]: 1. SMTP Server Close this session.'
+ end
+ code, err = read_packet(data)
+ if not code or code ~= 334 then
+ return nil, '[AUTH LOGIN ERROR]: 1. 验证失败('.. tostring(code) .. (err or '未知错误') ..')'
+ end
+ -- 发送base64用户名
+ if not self:sendline(base64encode(self.username), CRLF) then
+ return nil, "[AUTH LOGIN ERROR]: SMTP Server Close this session when sending username."
+ end
+ data = self:readline(CRLF)
+ if not data then
+ return nil, '[AUTH LOGIN ERROR]: 2. SMTP Server Close this session.'
+ end
+ code, err = read_packet(data)
+ if not code or code ~= 334 then
+ return nil, '[AUTH LOGIN ERROR]: 2. verification failed('.. (err or '未知错误') ..')'
+ end
+ -- 发送base64密码
+ if not self:sendline(base64encode(self.password), CRLF) then
+ return nil, "[AUTH LOGIN ERROR]: SMTP Server Close this session when sending password."
+ end
+ data = self:readline(CRLF)
+ if not data then
+ return nil, '[AUTH LOGIN ERROR]: 3. SMTP Server Close this session.'
+ end
+ code, err = read_packet(data)
+ if not code or code ~= 235 then
+ return nil, '[AUTH LOGIN ERROR]: ' .. (err or 'Token verification failed.')
+ end
+ return true
+end
+
+-- 发送邮件头部
+function smtp:send_header ()
+ local code, data, err
+ -- 发送邮件来源
+ if not self:sendline(fmt("MAIL FROM: <%s>", self.from), CRLF) then
+ return nil, "[MAIL FROM ERROR]: Sending `MAIL FROM` Failed."
+ end
+ data = self:readline(CRLF)
+ if not data then
+ return nil, '[MAIL FROM ERROR]: SMTP Server Close this session.'
+ end
+ code, err = read_packet(data)
+ if not code or code ~= 250 then
+ return nil, '[MAIL FROM ERROR]: ('.. tostring(code) .. (err or 'Unknown Error.') ..')'
+ end
+ -- 发送邮件接收者
+ local ok = self:sendline(fmt("RCPT TO: <%s>", self.to), CRLF)
+ if not ok then
+ return nil, "[RCPT TO ERROR]: Sending `RCPT TO` Failed."
+ end
+ data = self:readline(CRLF)
+ if not data then
+ return nil, '[RCPT TO ERROR]: SMTP Server Close this session.'
+ end
+ code, err = read_packet(data)
+ if not code or code ~= 250 then
+ return nil, '[RCPT TO ERROR]: ('.. tostring(code) .. (err or '未知错误') ..')'
+ end
+ return true
+end
+
+-- 发送邮件内容
+function smtp:send_content ()
+ local code, data, err
+ -- 发送DATA命令, 开始发送邮件实体
+ if not self:sendline("DATA", CRLF) then
+ return nil, "[MAIL CONTENT ERROR]: Sending `DATA` Failed."
+ end
+ data = self:readline(CRLF)
+ if not data then
+ return nil, '[MAIL CONTENT ERROR]: SMTP Server Close this session.'
+ end
+ code, err = read_packet(data)
+ if not code or code ~= 354 then
+ return nil, '[MAIL CONTENT ERROR]: ('.. tostring(code) .. (err or '未知错误') ..')'
+ end
+ if self.mime and self.mime == 'html' then
+ self.mime = "MIME-Version: 1.0\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: base64\r\n"
+ else
+ self.mime = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: base64\r\n"
+ end
+ -- 发送邮件实体头部
+ local ok = self:send(concat({fmt("From: %s <%s>\r\n", self.fromName or "", self.from), fmt("To: <%s>\r\n", self.to), fmt("Subject: %s\r\n", self.subject), self.mime, CRLF}))
+ if not ok then
+ return nil, "[MAIL CONTENT ERROR]: 发送Content Headers失败."
+ end
+ -- 发送邮件实体内容
+ if not self:sendline(base64encode(self.content), "\r\n\r\n.\r\n") then
+ return nil, "[MAIL CONTENT ERROR]: 发送Content Body失败."
+ end
+ data = self:readline(CRLF)
+ if not data then
+ return nil, time()..'[MAIL CONTENT ERROR]: ' .. (err or '服务器关闭了连接. ')
+ end
+ code, err = read_packet(data)
+ if not code or code ~= 250 then
+ return nil, time()..'[MAIL CONTENT ERROR]: ('.. tostring(code) .. (err or '未知错误') ..')'
+ end
+ return self:sendline("QUIT", CRLF)
+end
+
+function smtp:send_mail ()
+ local ok, err
+ ok, err = self:send_header()
+ if not ok then
+ return false, err
+ end
+ ok, err = self:send_content()
+ if not ok then
+ return false, err
+ end
+ return true
+end
+
+-- 超时时间
+function smtp:set_timeout (timeout)
+ if type(timeout) == 'number' and timeout > 0 then
+ self.timeout = timeout
+ end
+ return self
+end
+
+-- 连接到smtp服务器
+function smtp:connect ()
+ self.sock:timeout(self.timeout or 15)
+ if self.ssl then
+ return self.sock:ssl_connect(self.host, self.port)
+ end
+ return self.sock:connect(self.host, self.port)
+end
+
+-- 接收数据
+function smtp:recv (bytes)
+ if self.ssl then
+ return self.sock:ssl_recv(bytes)
+ end
+ return self.sock:recv(bytes)
+end
+
+-- 发送数据
+function smtp:send (data)
+ if self.ssl then
+ return self.sock:ssl_send(data)
+ end
+ return self.sock:send(data)
+end
+
+-- sendline
+function smtp:readline(sp)
+ if self.ssl then
+ return self.sock:ssl_readline(sp, true)
+ end
+ return self.sock:readline(sp, true)
+end
+
+-- sendline
+function smtp:sendline(data, sp)
+ return self.sock:send(data) and self.sock:send(sp)
+end
+
+function smtp:close ()
+ if self.sock then
+ self.sock:close()
+ self.sock = nil
+ end
+end
+
+return smtp
diff --git a/lualib/protocol/stomp/init.lua b/lualib/protocol/stomp/init.lua
new file mode 100644
index 00000000..d360a810
--- /dev/null
+++ b/lualib/protocol/stomp/init.lua
@@ -0,0 +1,138 @@
+local class = require "class"
+local tcp = require "internal.TCP"
+
+local cf = require "cf"
+local cf_fork = cf.fork
+
+local logging = require "logging"
+local Log = logging:new{ dump = true, path = "protocol-stomp"}
+
+local protocol = require "protocol.stomp.protocol"
+local protocol_send = protocol.send
+local protocol_subscribe = protocol.subscribe
+local protocol_unsubscribe = protocol.unsubscribe
+local protocol_disconnect = protocol.disconnect
+local connect_and_login = protocol.connect
+
+local type = type
+local assert = assert
+local fmt = string.format
+local random = math.random
+
+local stomp = class("stomp")
+
+function stomp:ctor (opt)
+ self.id = opt.id or fmt('luastomp-cf-v1-0x%08X', random(0xFFFFFFFF))
+ self.ssl = opt.ssl
+ self.sock = tcp:new()
+ self.host = opt.host
+ self.port = opt.port
+ self.header = opt.header
+ self.vhost = opt.vhost or "/exchange"
+ self.username = opt.username
+ self.password = opt.password
+end
+
+-- 连接
+function stomp:connect ()
+ -- 如果需要扩展头部
+ local opt = {
+ ['id'] = self.id,
+ ['client_id'] = self.id,
+ ['vhost'] = self.vhost,
+ ['login'] = self.username,
+ ['username'] = self.username,
+ ['passcode'] = self.password,
+ }
+ if type(self.header) == 'table' then
+ for key, value in pairs(self.header) do
+ opt[key] = value
+ end
+ end
+ -- 登录授权
+ local ok, data = connect_and_login(self, opt)
+ if not ok then
+ return ok, data
+ end
+ self.session = data.session
+ self.state = true
+ return true
+end
+
+function stomp:send (...)
+ return self:publish(...)
+end
+
+function stomp:publish (topic, payload)
+ if not self.state then
+ return nil, "[STOMP ERROR] : client not connected."
+ end
+ if type(topic) ~= 'string' or topic == '' or type(payload) ~= 'string' or payload == '' then
+ return nil, "[STOMP ERROR] : Invalide `topic` or `payload` arguments."
+ end
+ return protocol_send(self, { topic = topic, payload = payload })
+end
+
+function stomp:subscribe (topic, func)
+ if not self.state then
+ return nil, "[STOMP ERROR] : client not connected."
+ end
+ if type(topic) ~= 'string' or topic == '' or type(func) ~= 'function' then
+ return nil, '[STOMP ERROR] : Invalide `topic` or `func` arguments.'
+ end
+ local errinfo
+ assert(protocol_subscribe(self, topic))
+ cf_fork(function ()
+ while 1 do
+ local ok, msg = protocol_subscribe(self, topic, true)
+ if not ok then
+ Log:ERROR(msg)
+ ok, msg = pcall(func, false, msg)
+ if not ok then
+ Log:ERROR(msg)
+ end
+ return
+ end
+ ok, errinfo = pcall(func, {
+ len = msg['content-length'],
+ id = msg['message-id'],
+ session = msg['session'],
+ payload = msg['body'],
+ pattern = msg['destination'],
+ })
+ if not ok then
+ Log:ERROR(errinfo)
+ end
+ end
+ end)
+ return true
+end
+
+function stomp:unsubscribe ()
+ if not self.state then
+ return nil, "stomp未连接"
+ end
+ if not self.topic then
+ return nil, '没有需要取消订阅的topic'
+ end
+ return protocol_unsubscribe(self, self.topic)
+end
+
+function stomp:disconnect ()
+ if self.state then
+ self.state = nil
+ protocol_disconnect(self)
+ self:close()
+ end
+end
+
+-- 关闭
+function stomp:close ()
+ if self.sock then
+ self.state = nil
+ self.sock:close()
+ self.sock = nil
+ end
+end
+
+return stomp
diff --git a/lualib/protocol/stomp/protocol.lua b/lualib/protocol/stomp/protocol.lua
new file mode 100644
index 00000000..e706a834
--- /dev/null
+++ b/lualib/protocol/stomp/protocol.lua
@@ -0,0 +1,233 @@
+local pairs = pairs
+local toint = math.tointeger
+local splite = string.gmatch
+local insert = table.insert
+local concat = table.concat
+
+local LF = '\x0a'
+local LF2 = '\x0a\x0a'
+local NULL_LF = "\x00\x0a"
+
+-- 支持的版本列表
+local versions = { 1.2, 1.1, 1.0}
+
+local VERSION = {
+ ['1.2'] = 1.2,
+ ['1.1'] = 1.1,
+ ['1.0'] = 1.0,
+ ['1'] = 1.0,
+}
+
+local CMDS = {
+ ["CONNECTED"] = true,
+ ["SEND"] = true,
+ ["MESSAGE"] = true,
+ ["SUBSCRIBE"] = true,
+ ["UNSUBSCRIBE"] = true,
+ ["ABORT"] = true,
+ ["DISCONNECT"] = true,
+ ["ERROR"] = true,
+}
+
+-- 支持版本
+local function version_support (list)
+ for _, v in pairs(list) do
+ if VERSION[v] then
+ return true
+ end
+ end
+ return false
+end
+
+local function sock_send (sock, data)
+ return sock:send(data)
+end
+
+local function sock_read (sock, bytes)
+ local buffer = sock:recv(bytes)
+ if not buffer then
+ return
+ end
+ if #buffer == bytes then
+ return buffer
+ end
+ bytes = bytes - #buffer
+ local buffers = {buffer}
+ local sock_recv = sock.recv
+ while 1 do
+ buffer = sock_recv(sock, bytes)
+ if not buffer then
+ return
+ end
+ bytes = bytes - #buffer
+ insert(buffers, buffer)
+ if bytes == 0 then
+ return concat(buffers)
+ end
+ end
+end
+
+local function sock_readline(sock, sp, nosp)
+ return sock:readline(sp, nosp)
+end
+
+local function sock_connect (sock, ssl, host, port)
+ local ok, err = sock:connect(host, port)
+ if not ok then
+ return false, err
+ end
+ if ssl then
+ ok = sock:ssl_handshake()
+ if not ok then
+ return false, "[STOMP ERROR] : SSL handshake failed."
+ end
+ end
+ return true
+end
+
+local function parser_header (data)
+ local HEADERS = {}
+ for key, value in splite(data, "([^:]+):([^\x0a]+)[\x0a]?") do
+ if key:lower() == 'version' then
+ local tab = {}
+ for ver in splite(value, '([^,]+)') do
+ tab[#tab+1] = ver
+ end
+ value = tab
+ end
+ if key:lower() == 'heart-beat' then
+ local tab = {}
+ for num in splite(value, '([^,]+)') do
+ tab[#tab+1] = num
+ end
+ value = tab
+ end
+ HEADERS[key] = value
+ end
+ return HEADERS
+end
+
+local function build_frame (CMD, opt, body)
+ local req = { CMD, "version:" .. concat(versions, ',') }
+ for key, value in pairs(opt) do
+ req[#req+1] = key..":"..value
+ end
+ if body then
+ -- req[#req+1] = "content-type:text/plain;charset=utf-8"
+ req[#req+1] = "content-length:"..#body
+ end
+ return concat{concat(req, LF), LF2, (body or ''), NULL_LF}
+end
+
+local function read_response (sock)
+ local response_cmd = sock_readline(sock, LF, true)
+ if not response_cmd then
+ return false, "[STOMP ERROR] : Server Close this session when receiving `cmd` data."
+ end
+ if not CMDS[response_cmd] then
+ return false, "[STOMP ERROR] : client get Invalid `cmd` data : " .. response_cmd
+ end
+ -- print(response_cmd)
+ local response_header = sock_readline(sock, LF2)
+ if not response_header then
+ return false, "[STOMP ERROR] : Server Close this session when receiving `headers` data."
+ end
+ local response = parser_header(response_header)
+ local v = response['version'] or response['Version']
+ if not v or not version_support(v) then
+ -- print(v, version_support(v))
+ return false, "[STOMP ERROR] : Unsupported Stomp protocol version."
+ end
+ -- var_dump(response)
+ response["COMMAND"] = response_cmd
+ local body_len = toint(response['content-length'] or response['Content-length'] or response['Content-Length'])
+ if body_len and body_len > 0 then
+ -- print("长度: ", body_len)
+ local body = sock_read(sock, body_len)
+ if not body then
+ return false, "[STOMP ERROR] : Server Close this session when receiving `body` data."
+ end
+ -- print(body)
+ response['body'] = body
+ end
+ sock_readline(sock, NULL_LF)
+ -- var_dump(response)
+ return true, response
+end
+
+
+local protocol = {}
+
+
+-- 连接
+function protocol.connect (self, opt)
+ local ok = sock_connect(self.sock, self.ssl, self.host, self.port)
+ if not ok then
+ self.state = nil
+ return nil, "[STOMP ERROR] : Server connnect refuse."
+ end
+ if not sock_send(self.sock, build_frame("CONNECT", opt)) then
+ self.state = nil
+ return nil, '[STOMP ERROR] : client send `CONNECT` failed.'
+ end
+ return read_response(self.sock)
+end
+
+-- 发布消息
+function protocol.send (self, opt)
+ local ok = sock_send(self.sock, build_frame("SEND", { ['id'] = self.id, ['session'] = self.session, ['destination'] = self.vhost .. opt.topic }, opt.payload))
+ if not ok then
+ self.state = nil
+ return nil, '[STOMP ERROR] : `SEND` failed.'
+ end
+ return true
+end
+
+-- 订阅消息
+function protocol.subscribe (self, topic, already)
+ if not already then
+ local ok = sock_send(self.sock, build_frame("SUBSCRIBE", { ['id'] = self.id, ['session'] = self.session, ['destination'] = self.vhost .. topic }))
+ if not ok then
+ self.state = nil
+ return nil, '[STOMP ERROR] : `SUBSCRIBE` failed.'
+ end
+ self.topic = topic
+ return true
+ end
+ local ok, pack = read_response(self.sock)
+ if not ok then
+ self.state = nil
+ return nil, pack
+ end
+ self.topic = topic
+ return true, pack
+end
+
+-- 取消订阅
+function protocol.unsubscribe (self, topic)
+ local ok = sock_send(self.sock, build_frame("UNSUBSCRIBE", { ['id'] = self.id, ['session'] = self.session, ['destination'] = self.vhost .. topic }))
+ self.topic = nil
+ if not ok then
+ self.state = nil
+ return nil, '[STOMP ERROR] : `UNSUBSCRIBE` failed.'
+ end
+ return read_response(self.sock)
+end
+
+-- 回应
+function protocol.ack (self, opt)
+ local ok = sock_send(self.sock, build_frame("ACK", { ['id'] = self.id, ['session'] = self.session, ['message-id'] = opt['message-id'], ['transaction'] = opt['transaction'] }))
+ if not ok then
+ self.state = nil
+ return nil, "[STOMP ERROR] : `ACK` failed."
+ end
+ return true
+end
+
+-- 断开连接
+function protocol.disconnect (self)
+ self.state = nil
+ return sock_send(self.sock, build_frame("DISCONNECT", { ['receipt'] = 1 }))
+end
+
+return protocol
diff --git a/lualib/protocol/websocket/client.lua b/lualib/protocol/websocket/client.lua
new file mode 100644
index 00000000..5672c843
--- /dev/null
+++ b/lualib/protocol/websocket/client.lua
@@ -0,0 +1,296 @@
+local class = require "class"
+
+local log = require "logging"
+local Log = log:new{ dump = true, path = "protocol-wsclient"}
+
+local tcp = require "internal.TCP"
+local stream = require "stream"
+
+local cf = require "cf"
+local cf_fork = cf.fork
+
+local new_tab = require "sys".new_tab
+
+local wbproto = require "protocol.websocket.protocol"
+local _recv_frame = wbproto.recv_frame
+local _send_frame = wbproto.send_frame
+
+local HTTP = require "protocol.http"
+local PARSER_HTTP_RESPONSE = HTTP.PARSER_HTTP_RESPONSE
+
+local crypt = require "crypt"
+local sha1 = crypt.sha1
+local base64encode = crypt.base64encode
+
+local type = type
+local next = next
+local pcall = pcall
+local ipairs = ipairs
+
+local random = math.random
+local toint = math.tointeger
+local os_date = os.date
+local concat = table.concat
+local insert = table.insert
+
+local char = string.char
+local byte = string.byte
+local find = string.find
+local fmt = string.format
+local match = string.match
+local strpack = string.pack
+
+local CRLF = '\x0d\x0a'
+local CRLF2 = '\x0d\x0a\x0d\x0a'
+
+local function sock_readline (sock, sp)
+ return sock:readline(sp)
+end
+
+local function sock_send (self, data)
+ local sock = self.sock
+ if self.ssl then
+ return sock:ssl_send(data)
+ end
+ return sock:send(data)
+end
+
+local function sock_connect (self, domain, port)
+ local sock = self.sock
+ if self.ssl then
+ return sock:ssl_connect(domain, port)
+ end
+ return sock:connect(domain, port)
+end
+
+local function sock_close (self)
+ self.sock:close()
+ self.sock = nil
+end
+
+local function check_response (self, secure)
+
+ -- 读取协议头
+ local buffer = sock_readline(self.sock, CRLF2)
+ if not buffer then
+ return false, '[WS ERROR] : Server Close this session when recv http handshake.'
+ end
+
+ -- 解析协议头
+ local version, code, _, headers = PARSER_HTTP_RESPONSE(buffer)
+ if tonumber(version) ~= 1.1 or tonumber(code) ~= 101 or not headers then
+ sock_close(self)
+ return nil, "Error: protocol upgrade failed."
+ end
+ if not next(headers) then
+ sock_close(self)
+ return nil, "Error: unsupported response header."
+ end
+
+ -- 验证握手信息
+ local connection = headers['Connection']
+ if not connection or connection:lower() ~= 'upgrade' then
+ sock_close(self)
+ return nil, 'Error: Unsupported websocket protocol version.'
+ end
+ if headers['Sec-WebSocket-Accept'] ~= secure then
+ sock_close(self)
+ return nil, 'Error: `Sec-WebSocket-Accept` verification failed.'
+ end
+ if type(headers['Sec-WebSocket-Extensions']) == 'string' and find(headers['Sec-WebSocket-Extensions'], "permessage%-deflate") then
+ self.ext = 'deflate'
+ end
+
+ return true
+end
+
+-- HTTP[s] Over WebSocket Upgrade
+local function do_handshake (self)
+
+ local ok, err
+
+ ok, err = sock_connect(self, self.domain, self.port)
+ if not ok then
+ sock_close(self)
+ return nil, err
+ end
+
+ local key = char(
+ byte('c'), byte('f'), byte('a'), byte('d'), byte('m'), byte('i'), byte('n'),
+ random(256) - 1, random(256) - 1, random(256) - 1,
+ random(256) - 1, random(256) - 1, random(256) - 1,
+ random(256) - 1, random(256) - 1, random(256) - 1
+ )
+
+ local sec_key = base64encode(key)
+ local req = {
+ fmt('GET %s HTTP/1.1', self.path),
+ fmt('Data: %s', os_date("Date: %a, %d %b %Y %X GMT")),
+ fmt('Host: %s:%s', self.domain, self.port),
+ fmt('Sec-WebSocket-Key: %s', sec_key),
+ 'Origin: http://'..self.domain,
+ 'Upgrade: websocket',
+ 'Connection: Upgrade',
+ 'Sec-WebSocket-Version: 13',
+ 'User-Agent: cf-websocket/0.1',
+ 'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits',
+ CRLF
+ }
+
+ ok, err = sock_send(self, concat(req, CRLF))
+ if not ok then
+ sock_close(self)
+ return ok, err
+ end
+
+ return check_response(self, base64encode(sha1(sec_key .. '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')))
+end
+
+local function url_split (self)
+ local scheme, domain_port, path = match(self.url, '^(ws[s]?)://([^/]+)(.*)')
+ if not scheme or not domain_port then
+ return nil, "Connection failed: invalid url parameter."
+ end
+
+ if not path or path == '' then
+ path = '/'
+ end
+
+ local domain, port
+ if find(domain_port, ':') then
+ local _, Bracket_Pos = find(domain_port, '[%[%]]')
+ if Bracket_Pos then
+ domain, port = match(domain_port, '%[(.+)%][:]?(%d*)')
+ else
+ domain, port = match(domain_port, '([^:]+):(%d*)')
+ end
+ if not domain then
+ return nil, "Invalid or illegal hostname: "..domain_port
+ end
+ port = toint(port)
+ if not port then
+ port = scheme == 'wss' and 443 or 80
+ end
+ else
+ domain, port = domain_port, scheme == 'ws' and 80 or 443
+ end
+
+ -- 判断是否需要ssl socket
+ if scheme == 'wss' then
+ self.ssl = true
+ end
+
+ self.domain = domain
+ self.port = port
+ self.path = path
+ return true
+end
+
+local websocket = class("websocket-client")
+
+function websocket:ctor (opt)
+ self.ssl = nil
+ self.ext = nil
+ self.url = opt.url
+ self.sock = stream(tcp:new())
+ self.send_masked = true
+ self.sock._timeout = toint(opt.timeout)
+ self.max_payload_len = opt.max_payload_len or 65535
+end
+
+-- 设置超时
+function websocket:set_timeout (timeout)
+ self.sock._timeout = timeout
+end
+
+function websocket:connect ()
+ if self.state then
+ return nil, 'already connected.'
+ end
+ local ok, err
+ -- 切割URL
+ ok, err = url_split(self)
+ if not ok then
+ return nil, err
+ end
+ -- Websocket握手流程
+ ok, err = do_handshake(self)
+ if not ok then
+ return nil, err
+ end
+ self.state = true
+ return true, err
+end
+
+function websocket:request(func)
+ if not self.queue then
+ self.queue = new_tab(16, 0)
+ self.co = cf_fork(function ()
+ -- print("do")
+ for _, f in ipairs(self.queue) do
+ local ok, err = pcall(f)
+ if not ok then
+ Log:ERROR(err)
+ end
+ end
+ -- print("end")
+ self.co, self.queue = nil, nil
+ end)
+ end
+ return insert(self.queue, func)
+end
+
+-- 接受数据
+function websocket:recv()
+ if not self.state then
+ return nil, 'not connected.'
+ end
+ local data, typ, err = _recv_frame(self.sock, self.max_payload_len, false)
+ if not data then
+ self.state = nil
+ return false, err
+ end
+ return data, typ
+end
+
+-- 发送 text/binary
+function websocket:send (data, bin)
+ if not self.state then
+ return nil, 'not connected.'
+ end
+ assert(type(data) == 'string' and data ~= '', "Invalid websocket send data.")
+ local sock = self.sock
+ return _send_frame(sock, true, bin and 0x02 or 0x01, data, self.send_masked, self.ext)
+end
+
+-- 发送ping
+function websocket:ping(data)
+ if not self.state then
+ return nil, 'not connected.'
+ end
+ local sock = self.sock
+ return _send_frame(sock, true, 0x09, type(data) == 'string' and #data <= 125 and data or "", self.send_masked, self.ext)
+end
+
+-- 发送pong
+function websocket:pong(data)
+ if not self.state then
+ return nil, 'not connected.'
+ end
+ local sock = self.sock
+ return _send_frame(sock, true, 0x0A, type(data) == 'string' and #data <= 125 and data or "", self.send_masked, self.ext)
+end
+
+-- 清理连接
+function websocket:close ()
+ if self.sock then
+ local sock = self.sock
+ return self:request(function ()
+ if self.state then
+ return _send_frame(sock, true, 0x08, strpack(">H", 1000), self.send_masked, self.ext)
+ end
+ end)
+ end
+end
+
+return websocket
diff --git a/lualib/protocol/websocket/protocol.lua b/lualib/protocol/websocket/protocol.lua
index e5cae73a..18343a78 100644
--- a/lualib/protocol/websocket/protocol.lua
+++ b/lualib/protocol/websocket/protocol.lua
@@ -1,278 +1,235 @@
--- Copyright (C) Yichun Zhang (agentzh)
--- modify by CandyMi in 2019.1.12
-
-local byte = string.byte
-local char = string.char
-local sub = string.sub
-local concat = table.concat
-local str_char = string.char
-local rand = math.random
-local tostring = tostring
-local type = type
-
-local new_tab = function (narr, nrec) return {} end
-
-
-local _M = new_tab(0, 5)
-
-_M.new_tab = new_tab
-_M._VERSION = '0.07'
-
-
-local types = {
- [0x0] = "continuation",
- [0x1] = "text",
- [0x2] = "binary",
- [0x8] = "close",
- [0x9] = "ping",
- [0xa] = "pong",
-}
-
-
-function _M.recv_frame(sock, max_payload_len, force_masking)
- local data, err = sock:recv(2)
- if not data then
- return nil, nil, err
- end
-
- local fst, snd = byte(data, 1, 2)
-
- local fin = fst & 0x80 ~= 0
-
- if fst & 0x70 ~= 0 then
- return nil, nil, "bad RSV1, RSV2, or RSV3 bits"
- end
-
- local opcode = fst & 0x0f
-
- if opcode >= 0x3 and opcode <= 0x7 then
- return nil, nil, "reserved non-control frames"
- end
-
- if opcode >= 0xb and opcode <= 0xf then
- return nil, nil, "reserved control frames"
- end
-
- local mask = snd & 0x80 ~= 0
-
- if force_masking and not mask then
- return nil, nil, "frame unmasked"
- end
-
- local payload_len = snd & 0x7f
-
- if payload_len == 126 then
- local data, err = sock:recv(2)
- if not data then
- return nil, nil, "failed to receive the 2 byte payload length: "
- .. (err or "unknown")
- end
- payload_len = (byte(data, 1) >> 8) | byte(data, 2)
-
- elseif payload_len == 127 then
- local data, err = sock:recv(8)
- if not data then
- return nil, nil, "failed to receive the 8 byte payload length: "
- .. (err or "unknown")
- end
-
- if byte(data, 1) ~= 0
- or byte(data, 2) ~= 0
- or byte(data, 3) ~= 0
- or byte(data, 4) ~= 0
- then
- return nil, nil, "payload len too large"
- end
-
- local fifth = byte(data, 5)
- if fifth & 0x80 ~= 0 then
- return nil, nil, "payload len too large"
- end
- payload = fifth << 24 | byte(data, 6) << 16 | byte(data, 7) | byte(data, 8)
- end
-
- if opcode & 0x8 ~= 0 then
- -- being a control frame
- if payload_len > 125 then
- return nil, nil, "too long payload for control frame"
- end
-
- if not fin then
- return nil, nil, "fragmented control frame"
- end
- end
-
- -- print("payload len: ", payload_len, ", max payload len: ",
- -- max_payload_len)
-
- if payload_len > max_payload_len then
- return nil, nil, "exceeding max payload len"
- end
-
- local rest
- if mask then
- rest = payload_len + 4
-
- else
- rest = payload_len
- end
- -- print("rest: ", rest)
-
- local data
- if rest > 0 then
- data, err = sock:recv(rest)
- if not data then
- return nil, nil, "failed to read masking-len and payload: "
- .. (err or "unknown")
- end
- else
- data = ""
- end
-
- if opcode == 0x8 then
-
- if payload_len > 0 then
- if payload_len < 2 then
- return nil, nil, "close frame with a body must carry a 2-byte status code"
- end
-
- local msg, code
- if mask then
- local fst = byte(data, 4 + 1) ~ byte(data, 1)
- local snd = byte(data, 4 + 2) ~ byte(data, 2)
- code = (fst << 8) | snd
-
- if payload_len > 2 then
- -- TODO string.buffer optimizations
- local bytes = new_tab(payload_len - 2, 0)
- for i = 3, payload_len do
- bytes[i - 2] = str_char(byte(data, 4 + i) ~ byte(data, (i - 1) % 4 + 1))
- end
- msg = concat(bytes)
-
- else
- msg = ""
- end
-
- else
- local fst = byte(data, 1)
- local snd = byte(data, 2)
- code = (fst << 8) | snd
-
- if payload_len > 2 then
- msg = sub(data, 3)
-
- else
- msg = ""
- end
- end
-
- return msg, "close"
- end
- return "", "close", nil
- end
-
- local msg
- if mask then
- -- TODO string.buffer optimizations
- local bytes = new_tab(payload_len, 0)
- for i = 1, payload_len do
- bytes[i] = str_char(byte(data, 4 + i) ~ byte(data, (i - 1) % 4 + 1))
- end
- msg = concat(bytes)
-
- else
- msg = data
- end
-
- return msg, types[opcode], not fin and "again" or nil
-end
-
-
-local function build_frame(fin, opcode, payload_len, payload, masking)
- -- XXX optimize this when we have string.buffer in LuaJIT 2.1
- local fst
- if fin then
- fst = 0x80 | opcode
- else
- fst = opcode
- end
-
- local snd, extra_len_bytes
- if payload_len <= 125 then
- snd = payload_len
- extra_len_bytes = ""
-
- elseif payload_len <= 65535 then
- snd = 126
- extra_len_bytes = char((payload_len >> 8) & 0xff, payload_len & 0xff)
-
- else
- if payload_len & 0x7fffffff < payload_len then
- return nil, "payload too big"
- end
-
- snd = 127
- -- XXX we only support 31-bit length here
- extra_len_bytes = char(0, 0, 0, 0,
- (payload_len >> 24) & 0xff,
- (payload_len >> 16) & 0xff,
- (payload_len >> 8) & 0xff,
- payload_len & 0xff)
- end
-
- local masking_key
- if masking then
- -- set the mask bit
- snd = snd | 0x80
- local key = rand(0xffffffff)
- masking_key = char((key >> 24)& 0xff, (key >> 16)& 0xff, (key >> 8)& 0xff, key & 0xff)
-
- -- TODO string.buffer optimizations
- local bytes = new_tab(payload_len, 0)
- for i = 1, payload_len do
- bytes[i] = str_char(byte(payload, i) ~ byte(masking_key, (i - 1) % 4 + 1))
- end
- payload = concat(bytes)
-
- else
- masking_key = ""
- end
-
- return char(fst, snd) .. extra_len_bytes .. masking_key .. payload
-end
-_M.build_frame = build_frame
-
-
-function _M.send_frame(sock, fin, opcode, payload, max_payload_len, masking)
-
- if not payload then
- payload = ""
-
- elseif type(payload) ~= "string" then
- payload = tostring(payload)
- end
-
- local payload_len = #payload
-
- if payload_len > max_payload_len then
- return nil, "payload too big"
- end
-
- if opcode & 0x8 ~= 0 then
- if payload_len > 125 then
- return nil, "too much payload for control frame"
- end
- if not fin then
- return nil, "fragmented control frame"
- end
- end
-
- local frame, err = build_frame(fin, opcode, payload_len, payload, masking)
- if not frame then
- return nil, "failed to build frame: " .. err
- end
- return sock:send(frame)
-end
-
-return _M
\ No newline at end of file
+local crypt = require "crypt"
+local xor_str = crypt.xor_str
+
+local lz = require "lz"
+local wscompress = lz.wscompress
+local wsuncompress = lz.wsuncompress
+
+local new_tab = require("sys").new_tab
+
+local error = error
+local assert = assert
+
+local strpack = string.pack
+local strunpack = string.unpack
+local random = math.random
+local concat = table.concat
+local insert = table.insert
+
+local WS_TYPE = {
+ [0x00] = "continuation",
+ [0x01] = "text",
+ [0x02] = "binary",
+ [0x08] = "close",
+ [0x09] = "ping",
+ [0x0A] = "pong",
+}
+
+local function sock_recv (sock, bytes)
+ local buf = sock:readbytes(bytes)
+ if not buf then
+ sock.closed = true
+ end
+ return buf
+end
+
+local function sock_send (sock, data)
+ return sock:write(data)
+end
+
+local function wsdeflate(data)
+ return wscompress(data)
+end
+
+local function wsinflate(data)
+ return wsuncompress(data)
+end
+
+
+local protocol = { __VERSION__ = 0.1 }
+
+---@comment 接收数据
+---@param sock table @socket对象
+---@param max_payload_len integer @最大长度限制
+---@param force_masking boolean @强制检查掩码
+---@param buffers? table @内部的缓冲区
+---@return string | boolean @消息数据
+---@return string? @消息类型
+---@return string? @错误信息
+function protocol.recv_frame(sock, max_payload_len, force_masking, buffers)
+ local hdata = sock_recv(sock, 2)
+ if not hdata then
+ return false, 'close'
+ end
+
+ local h1, h2 = strunpack("BB", hdata)
+ -- 检查协议头部是否符合规范
+ local fin, rsv = h1 & 0x80 == 0x80, (h1 >> 4) & 0x07
+ if rsv ~= 0x00 and rsv ~= 0x04 then
+ return false, 'error', "[WS ERROR] : Invalid RSV1 or RSV2 or RSV3."
+ end
+
+ -- 检查 OPCODE 是否有效
+ local opcode = WS_TYPE[h1 & 0x0F]
+ if not opcode then
+ return false, 'error', "[WS ERROR] : received Invalid opcode."
+ end
+
+ -- 判断数据载荷的实际长度
+ local plen = h2 & 0x7F
+ if plen == 126 then
+ local body_size_str = sock_recv(sock, 2)
+ if not body_size_str then
+ return false, 'error', "[WS ERROR] : Client Close this session when read 2 bytes body size."
+ end
+ plen = strunpack(">I2", body_size_str)
+ elseif plen == 127 then
+ local body_size_str = sock_recv(sock, 8)
+ if not body_size_str then
+ return false, 'error', "[WS ERROR] : Client Close this session when read 8 bytes body size."
+ end
+ plen = strunpack(">I8", body_size_str)
+ end
+
+ local mask_key = nil
+ -- 如果最高位是1表明PAYLOAD有掩码位.
+ if h2 & 0x80 == 0x80 then
+ mask_key = sock_recv(sock, 4)
+ if not mask_key then
+ return false, 'error', "[WS ERROR] : Client Close this session when read mask_key data."
+ end
+ end
+
+ -- 如果强制要求但是还是未检查
+ if force_masking and not mask_key then
+ return false, 'error', "[WS ERROR] : Mask must be present."
+ end
+
+ -- 需要提前断言数据长度是否超出限制
+ if plen >= max_payload_len then
+ return false, 'error', "[WS ERROR] : Content exceeding the length limit."
+ end
+
+ -- 控制帧必须不允许有扩展长度
+ if (opcode == 'close' or opcode == 'ping' or opcode == 'pong') and plen > 125 then
+ return false, 'error', "[WS ERROR] : The payload length of the control frame is too long."
+ end
+
+ local data = ""
+
+ -- 读取数据载荷
+ if plen > 0 then
+
+ data = sock_recv(sock, plen)
+ if not data then
+ return false, 'error', "[WS ERROR] : Client Close this session when read real payload."
+ end
+
+ -- 如果还有后续的`CONTINUATION`帧
+ if not fin or opcode == 'continuation' then
+
+ -- 等所有buffer读取完毕后再连接起来
+ if not buffers then
+ buffers = new_tab(8, 0)
+ end
+ buffers[#buffers+1] = data
+
+ -- 接受数据完毕.
+ if opcode == 'continuation' and fin then
+ return true
+ end
+
+ -- 接受数据期间产生错误, 则不再处理后续数据.
+ local d, typ, errinfo = protocol.recv_frame(sock, max_payload_len, force_masking, buffers)
+ if not d then
+ return false, typ, errinfo
+ end
+ data = concat(buffers)
+
+ end
+
+ -- 有掩码位需要异或还原数据.
+ if mask_key then
+ data = xor_str(data, mask_key)
+ end
+
+ -- 支持 permessage-deflate 必须解压缩数据载荷
+ if rsv == 0x04 then
+ local buf = wsinflate(data)
+ if not buf then
+ return false, 'error', "[WS ERROR] : received Invalid deflate buffers."
+ end
+ data = buf
+ end
+
+ -- close帧有状态码
+ if opcode == 'close' then
+ data = data:sub(3)
+ end
+
+ end
+
+ return data, opcode
+end
+
+---@comment 发送数据
+---@param sock table @socket对象
+---@param fin boolean @结束帧标志
+---@param opcode integer @消息类型
+---@param payload string @数据载荷
+---@param masking boolean @数据掩码
+---@param ext string @协议扩展
+function protocol.send_frame(sock, fin, opcode, payload, masking, ext)
+
+ local payload_len = #payload
+
+ local opc = assert(WS_TYPE[opcode], "[WS ERROR] : attempted pass invalid websocket opcode.")
+ if opc == 'close' or opc == 'ping' or opc == 'pong' then
+ if payload_len > 125 then
+ return error("[WS ERROR] : The payload length of the control frame is too long.")
+ end
+ end
+
+ -- 结束位标志位 + 保留位 + 消息类型
+ local h1 = (fin and 0x80 or 0x00) | opcode
+ -- 如果有扩展协议则加上扩展响应头部
+ if (opc ~= 'close' and opc ~= 'ping' and opc ~= 'pong') and payload_len > 125 and ext == 'deflate' then
+ h1 = h1 | 0x40
+ payload = wsdeflate(payload)
+ payload_len = #payload
+ end
+
+ -- 掩码位与长度位
+ local h2 = masking and 0x80 or 0x00
+ local len_ext
+ if payload_len < 126 then
+ h2 = h2 | payload_len
+ elseif payload_len < 65536 then
+ h2, len_ext = h2 | 0x7E, strpack(">I2", payload_len)
+ else
+ h2, len_ext = h2 | 0x7F, strpack(">I8", payload_len)
+ end
+
+ local idx = 1
+ local buffers = new_tab(4, 0)
+ buffers[idx] = strpack(">BB", h1, h2)
+
+ if len_ext then
+ idx = idx + 1
+ buffers[idx] = len_ext
+ end
+
+ -- 创建随机掩码并与数据载荷进行异或
+ if masking and payload_len > 0 then
+ local mask = strpack(">BBBB", random(255), random(255), random(255), random(255))
+ payload = xor_str(payload, mask)
+ idx = idx + 1
+ buffers[idx] = mask
+ end
+
+ -- 根据实际情况需要减少发送数据次数.
+ buffers[idx + 1] = payload
+ sock_send(sock, concat(buffers))
+end
+
+return protocol
\ No newline at end of file
diff --git a/lualib/protocol/websocket/server.lua b/lualib/protocol/websocket/server.lua
index 58da2d36..098c7565 100644
--- a/lualib/protocol/websocket/server.lua
+++ b/lualib/protocol/websocket/server.lua
@@ -1,177 +1,113 @@
-local log = require "log"
-local class = require "class"
-local co = require "internal.Co"
-local wbproto = require "protocol.websocket.protocol"
-local _recv_frame = wbproto.recv_frame
-local _send_frame = wbproto.send_frame
+local stream = require "stream"
+
+local cf = require "cf"
+local cf_fork = cf.fork
+local cf_sleep = cf.sleep
+
+local wsproto = require "protocol.websocket.protocol"
+local _recv_frame = wsproto.recv_frame
+local _send_frame = wsproto.send_frame
-local co_self = co.self
-local co_wait = co.wait
-local co_spwan = co.spwan
-local co_wakeup = co.wakeup
+local LOG = require "logging":new { dump = true, path = 'protocol-websocket-server'}
local type = type
-local next = next
local pcall = pcall
local ipairs = ipairs
local assert = assert
-local setmetatable = setmetatable
-local tostring = tostring
-local char = string.char
+local insert = table.insert
+local strpack = string.pack
+local class = require "class"
-local websocket = class("websocket-server")
+local ws = class("ws")
-function websocket:ctor(opt)
- self.cls = opt.cls
- self.sock = opt.sock
- self.sock._timeout = nil
+function ws:ctor(opt)
+ self.ext = opt.ext
+ self.sock = opt.sock
+ self.closed = false
+ self.max_payload_len = 65535
end
--- 将回调函数写入到队列内
-local function add_to_queue(queue, func)
- queue[#queue + 1] = func
+function ws:set_timeout(timeout)
+ self.sock:timeout(timeout)
end
--- 一次将多条回调函数写入到队列内
-local function more_add_to_queue(queue, list)
- for _, func in ipairs(list) do
- add_to_queue(queue, func)
- end
+-- 设置发送掩码
+function ws:set_send_masked(send_masked)
+ self.send_masked = send_masked
end
--- 唤醒write queue
-local function wakeup(co)
- return co and co_wakeup(co)
+-- 设置最大数据载荷长度
+function ws:set_max_payload_len(max_payload_len)
+ self.max_payload_len = max_payload_len
end
-function websocket:start()
- local cls
- local sock = self.sock
- local current_co = co_self()
- local write_list = {}
- local write_co = co_spwan(function (...)
- while 1 do
- for _, f in ipairs(write_list) do
- local ok, err = pcall(f)
- if not ok then
- log.error(err)
- end
- end
- write_list = {}
- co_wait()
- if #write_list == 0 then
- -- print("写入协程退出了")
- return
- end
- end
- end)
- local ws = {
- CLOSE = false,
- send = function (self, data, binary)
- if self.CLOSE then return end
- if data and type(data) == 'string' then
- local code = 0x1
- if binary then
- code = 0x2
- end
- add_to_queue(write_list, function ()
- return _send_frame(
- sock,
- true,
- code,
- data,
- cls.max_payload_len or 65535,
- cls.send_masked or false
- )
- end)
- return wakeup(write_co)
- end
- end,
- close = function (self, data)
- if self.CLOSE then return end
- self.CLOSE = true
- more_add_to_queue(write_list, {
- function()
- return _send_frame(
- sock,
- true,
- 0x8,
- char(((1000 >> 8) & 0xff),(1000 & 0xff))..(data or ""),
- cls.max_payload_len or 65535,
- cls.send_masked or false
- )
- end,
- function() return sock:close() end,
- })
- return wakeup(current_co), wakeup(write_co)
- end,
- -- ping = function (self, data)
- -- if self.CLOSE then return end
- -- add_to_queue(write_list, function()
- -- _send_frame(sock, true, 0x9, data, cls.max_payload_len or 65535, cls.send_masked or false)
- -- end)
- -- return wakeup(write_co)
- -- end,
- -- pong = function (self, data)
- -- if self.CLOSE then return end
- -- add_to_queue(write_list, function()
- -- _send_frame(sock, true, 0xa, data, cls.max_payload_len or 65535, cls.send_masked or false)
- -- end)
- -- return wakeup(write_co)
- -- end,
- }
- cls = self.cls:new { ws = ws }
- local on_open = cls.on_open
- local on_message = cls.on_message
- local on_error = cls.on_error
- local on_close = cls.on_close
- local ok, err = pcall(on_open, cls)
- if not ok then
- log.error(err)
- return sock:close()
+-- 发送TEXT/BINARY帧
+function ws:send(data, binary)
+ if self.closed or self.sock.closed then
+ return
+ end
+ _send_frame(self.sock, true, binary and 0x02 or 0x01, data, false, self.ext)
+end
+
+-- 发送PING帧
+function ws:ping(data)
+ if self.closed or self.sock.closed then
+ return
+ end
+ return _send_frame(self.sock, true, 0x09, data or '', false, self.ext)
+end
+
+-- 发送CLOSE帧
+function ws:close(data)
+ if self.closed or self.sock.closed then
+ return
+ end
+ self.closed = true
+ _send_frame(self.sock, true, 0x08, strpack(">H", 1000) .. (type(data) == 'string' and data or ""), false, self.ext)
+end
+
+local Websocket = { __Version__ = 0.1 }
+
+function Websocket.start(sock, cls, args, headers, ext)
+ local on_open = assert(type(cls.on_open) == 'function' and cls.on_open, "'on_open' method is not implemented.")
+ local on_message = assert(type(cls.on_message) == 'function' and cls.on_message, "'on_message' method is not implemented.")
+ local on_error = assert(type(cls.on_error) == 'function' and cls.on_error, "'on_error' method is not implemented.")
+ local on_close = assert(type(cls.on_close) == 'function' and cls.on_close, "'on_close' method is not implemented.")
+
+ sock = stream(sock)
+ local w = ws { sock = sock, ext = ext }
+ local obj = cls{ ws = w, args = args, headers = headers }
+
+ local timeout = obj.timeout or 0
+ local max_payload_len = obj.max_payload_len or 65535
+ w:set_timeout(timeout); w:set_max_payload_len(max_payload_len)
+
+ local ok, err = pcall(on_open, obj)
+ if not ok then
+ return LOG:ERROR(err)
+ end
+ -- 开始监听
+ :: CONTINUE ::
+ local data, typ, errinfo = _recv_frame(sock, max_payload_len, true)
+ if not typ or typ == 'close' or typ == 'error' then
+ if typ == 'error' then
+ ok, err = pcall(on_error, obj, errinfo)
+ if not ok then
+ LOG:ERROR(err)
+ end
end
- while 1 do
- local data, typ, err = _recv_frame(sock, cls.max_payload_len, true)
- if (not data and not typ) or typ == 'close' then
- -- 客户端主动关闭: ws.CLOSE == flase
- -- 服务端主动关闭: ws.CLOSE == true
- if not ws.CLOSE then
- ws.CLOSE = true
- write_list = {}
- sock:close()
- end
- if err then
- local ok, err = pcall(on_error, cls, err)
- if not ok then
- log.error(err)
- end
- end
- local ok, err = pcall(on_close, cls, data)
- if not ok then
- log.error(err)
- end
- -- print("读取协程退出了")
- return wakeup(write_co)
- end
- if typ == 'ping' then
- add_to_queue(write_list, function()
- _send_frame(
- sock,
- true,
- 0xa,
- data,
- cls.max_payload_len or 65535,
- cls.send_masked or false
- )
- end)
- elseif typ == 'text' or typ == 'binary' then
- co_spwan(on_message, cls, data, typ)
- else
- -- 目前将设计精简为: 除了需要回应的ping协议, 其他协议均不会触发任何Server端回调响应;
- -- 如需特殊需求, 请自行在业务逻辑中解决(或使用定时器进行循环探测);
- end
+ ok, err = pcall(on_close, obj, data or errinfo)
+ if not ok then
+ LOG:ERROR(err)
end
+ return w:close(), cf_sleep(0)
+ elseif typ == 'ping' then
+ _send_frame(sock, true, 0x0A, data or '', false, ext)
+ elseif typ == 'text' or typ == 'binary' then
+ cf_fork(on_message, obj, data, typ == 'binary')
+ end
+ goto CONTINUE
end
-return websocket
\ No newline at end of file
+return Websocket
\ No newline at end of file
diff --git a/lualib/stream/init.lua b/lualib/stream/init.lua
new file mode 100644
index 00000000..6e1ff7ed
--- /dev/null
+++ b/lualib/stream/init.lua
@@ -0,0 +1,184 @@
+local TCP = require "internal.TCP"
+local sock_read = TCP.recv
+local sock_write = TCP.send
+local sock_readline = TCP.readline
+local sock_connect = TCP.connect
+local sock_connectx = TCP.connect_ex
+local sock_sslconnect = TCP.ssl_connect
+
+local new_tab = require "sys".new_tab
+
+local cf = require "cf"
+local cf_fork = cf.fork
+
+local type = type
+local error = error
+local ipairs = ipairs
+local getmetatable = getmetatable
+
+local mtype = math.type
+local strfmt = string.format
+local tconcat = table.concat
+
+local class = require "class"
+
+local Stream = class("Stream")
+
+function Stream:ctor(sock)
+ if getmetatable(sock) ~= TCP then
+ error(strfmt("[Stream Error]: Invalid Socket object in (%s:%d).", debug.getinfo(3, "S").source, debug.getinfo(3, "l").currentline))
+ end
+ self.tcp = sock
+end
+
+function Stream:set_fd(fd)
+ self.tcp:set_fd(fd)
+ return self
+end
+
+function Stream:timeout(ts)
+ self.tcp:timeout(ts)
+ return self
+end
+
+function Stream:connect(domain, port)
+ return sock_connect(self.tcp, domain, port)
+end
+
+function Stream:ssl_connect(domain, port)
+ return sock_sslconnect(self.tcp, domain, port)
+end
+
+function Stream:connectx(path)
+ return sock_connectx(self.tcp, path)
+end
+
+---comment @同步写入(阻塞当前协程)
+---@param buf string @待写入的数据
+---@return boolean @写入成功返回`true`, 写入失败返回`false`
+function Stream:send(buf)
+ if type (buf) ~= 'string' or buf == '' then
+ error(strfmt("[Stream Error]: pass Invalid send buffer in (%s:%d)", debug.getinfo(2, "S").source, debug.getinfo(2, "l").currentline))
+ end
+ return sock_write(self.tcp, buf)
+end
+
+---comment @异步写入(不会阻塞当前协程)
+---@param buf string @待写入的数据
+function Stream:write(buf)
+ if type (buf) ~= 'string' or buf == '' then
+ error(strfmt("[Stream Error]: pass Invalid write buffer in (%s:%d)", debug.getinfo(2, "S").source, debug.getinfo(2, "l").currentline))
+ end
+ -- 异步写入队列
+ if not self.wqueue then
+ self.wqueue = new_tab(8, 0)
+ cf_fork(function ()
+ local sock = self.tcp
+ if sock and self.wqueue then
+ for _, buffer in ipairs(self.wqueue) do
+ if not sock_write(sock, buffer) then
+ break
+ end
+ end
+ end
+ self.wqueue = nil
+ end)
+ end
+ self.wqueue[#self.wqueue+1] = buf
+end
+
+function Stream:recv(nbytes)
+ return self:read(nbytes)
+end
+
+---comment @读取指定数量的网络数据(此函数只要缓冲区里有数据立刻返回)
+---@param nbytes integer @指定的要读取的数量.
+---@return string | nil @读取成功返回内容, 失败返回`nil`
+function Stream:read(nbytes)
+ if mtype(nbytes) ~= 'integer' or nbytes < 1 then
+ error(strfmt("[Stream Error]: Pass invalid nbytes in (%s:%d)", debug.getinfo(2, "S").source, debug.getinfo(2, "l").currentline))
+ end
+ return sock_read(self.tcp, nbytes)
+end
+
+---comment @读取数据直到遇到指定分隔符, 可以选择返回的数据不包括分隔符.
+---@param sp string @字符串类型的分隔符
+---@param nosp boolean @返回数据是否包括分隔符
+---@return string | nil @读取成功返回内容, 失败返回`nil`
+function Stream:readline(sp, nosp)
+ if type(sp) ~= 'string' or sp == '' then
+ error(strfmt("[Stream Error]: Pass invalid readline char in (%s:%d)", debug.getinfo(2, "S").source, debug.getinfo(2, "l").currentline))
+ end
+ return sock_readline(self.tcp, sp, nosp)
+end
+
+---comment @读取指定数量的网络数据(读取足够字节或者连接断开才会返回).
+---@param nbytes integer @指定的要读取的数量.
+---@return string | nil @读取成功返回内容, 失败返回`nil`
+function Stream:readbytes(nbytes)
+ if mtype(nbytes) ~= 'integer' or nbytes < 1 then
+ error(strfmt("[Stream Error]: Pass invalid nbytes in (%s:%d)", debug.getinfo(2, "S").source, debug.getinfo(2, "l").currentline))
+ end
+ local sock, buffer, len, buffers = self.tcp, nil, nil, nil
+ :: CONTINUE ::
+ buffer, len = sock_read(sock, nbytes)
+ if not buffer then
+ return
+ end
+ -- 检查读取的字节数
+ if len == nbytes then
+ -- 如果是一次性读取完毕直接返回
+ if not buffers then
+ return buffer
+ end
+ -- 如果是多次读取完毕
+ buffers[#buffers+1] = buffer
+ return tconcat(buffers)
+ end
+ if not buffers then
+ buffers = new_tab(3, 0)
+ end
+ -- 计算字节数并且准备继续读取
+ buffers[#buffers+1] = buffer
+ nbytes = nbytes - len
+ goto CONTINUE
+end
+
+---comment @监听网络套接字
+---@param ip string @监听指定地址
+---@param port integer @监听指定端口
+---@param cb function @连接建立成功的回调
+function Stream:listen(ip, port, cb)
+ self.tcp:listen(ip, port, function (fd, ...)
+ return cb(Stream(TCP():set_fd(fd):timeout(0)), ...)
+ end)
+end
+
+---comment @监听本机套接字
+---@param path string @本机套接字所在路径
+---@param cb function @连接建立成功的回调
+function Stream:listenx(path, cb)
+ self.tcp:listen_ex(path, function (fd, ...)
+ return cb(Stream(TCP():set_fd(fd):timeout(0)), ...)
+ end)
+end
+
+function Stream:run_forever()
+ return cf.wait()
+end
+
+function Stream:run()
+ return self:run_forever()
+end
+
+function Stream:close()
+ if self.tcp then
+ self.tcp:close()
+ self.tcp = nil
+ end
+ if self.wqueue then
+ self.wqueue = nil
+ end
+end
+
+return Stream
\ No newline at end of file
diff --git a/lualib/system/init.lua b/lualib/system/init.lua
index fb9895a9..ff846674 100644
--- a/lualib/system/init.lua
+++ b/lualib/system/init.lua
@@ -1,17 +1,19 @@
local sys = require "sys"
-
-local string = string
-local fmt = string.format
-
+local now = sys.now
local is_ipv4 = sys.ipv4
local is_ipv6 = sys.ipv6
-local now = sys.now
local type = type
+local pairs = pairs
+local ipairs = ipairs
local os_date = os.date
local os_time = os.time
+local modf = math.modf
+
+local fmt = string.format
+
local System = {
-- 类型转换函数
toint = math.modf,
@@ -26,7 +28,7 @@ function System.is_int(number)
if type(number) ~= 'number' then
return false
end
- local int, float = math.modf(number)
+ local int, float = modf(number)
return float == 0.
end
@@ -35,7 +37,7 @@ function System.is_float(number)
if type(number) ~= 'number' then
return false
end
- local int, float = math.modf(number)
+ local int, float = modf(number)
return float ~= 0.
end
diff --git a/lualib/template.lua b/lualib/template.lua
deleted file mode 100644
index 79378113..00000000
--- a/lualib/template.lua
+++ /dev/null
@@ -1,478 +0,0 @@
-local setmetatable = setmetatable
-local loadstring = loadstring
-local loadchunk
-local tostring = tostring
-local setfenv = setfenv
-local require = require
-local capture
-local concat = table.concat
-local assert = assert
-local prefix
-local write = io.write
-local pcall = pcall
-local phase
-local open = io.open
-local load = load
-local type = type
-local dump = string.dump
-local find = string.find
-local gsub = string.gsub
-local byte = string.byte
-local null
-local sub = string.sub
-local ngx = ngx
-local jit = jit
-local var
-
-local _VERSION = _VERSION
-local _ENV = _ENV
-local _G = _G
-
-local HTML_ENTITIES = {
- ["&"] = "&",
- ["<"] = "<",
- [">"] = ">",
- ['"'] = """,
- ["'"] = "'",
- ["/"] = "/"
-}
-
-local CODE_ENTITIES = {
- ["{"] = "{",
- ["}"] = "}",
- ["&"] = "&",
- ["<"] = "<",
- [">"] = ">",
- ['"'] = """,
- ["'"] = "'",
- ["/"] = "/"
-}
-
-local VAR_PHASES
-
-local ok, newtab = pcall(require, "table.new")
-if not ok then newtab = function() return {} end end
-
-local caching = true
-local template = newtab(0, 12)
-
-template._VERSION = "1.9"
-template.cache = {}
-
-local function enabled(val)
- if val == nil then return true end
- return val == true or (val == "1" or val == "true" or val == "on")
-end
-
-local function trim(s)
- return gsub(gsub(s, "^%s+", ""), "%s+$", "")
-end
-
-local function rpos(view, s)
- while s > 0 do
- local c = sub(view, s, s)
- if c == " " or c == "\t" or c == "\0" or c == "\x0B" then
- s = s - 1
- else
- break
- end
- end
- return s
-end
-
-local function escaped(view, s)
- if s > 1 and sub(view, s - 1, s - 1) == "\\" then
- if s > 2 and sub(view, s - 2, s - 2) == "\\" then
- return false, 1
- else
- return true, 1
- end
- end
- return false, 0
-end
-
-local function readfile(path)
- local file = open(path, "rb")
- if not file then return nil end
- local content = file:read "*a"
- file:close()
- return content
-end
-
-local function loadlua(path)
- return readfile(path) or path
-end
-
-local function loadngx(path)
- local vars = VAR_PHASES[phase()]
- local file, location = path, vars and var.template_location
- if sub(file, 1) == "/" then file = sub(file, 2) end
- if location and location ~= "" then
- if sub(location, -1) == "/" then location = sub(location, 1, -2) end
- local res = capture(concat{ location, '/', file})
- if res.status == 200 then return res.body end
- end
- local root = vars and (var.template_root or var.document_root) or prefix
- if sub(root, -1) == "/" then root = sub(root, 1, -2) end
- return readfile(concat{ root, "/", file }) or path
-end
-
-do
- if ngx then
- VAR_PHASES = {
- set = true,
- rewrite = true,
- access = true,
- content = true,
- header_filter = true,
- body_filter = true,
- log = true
- }
- template.print = ngx.print or write
- template.load = loadngx
- prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase
- if VAR_PHASES[phase()] then
- caching = enabled(var.template_cache)
- end
- else
- template.print = write
- template.load = loadlua
- end
- if _VERSION == "Lua 5.1" then
- local context = { __index = function(t, k)
- return t.context[k] or t.template[k] or _G[k]
- end }
- if jit then
- loadchunk = function(view)
- return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
- end
- else
- loadchunk = function(view)
- local func = assert(loadstring(view))
- setfenv(func, setmetatable({ template = template }, context))
- return func
- end
- end
- else
- local context = { __index = function(t, k)
- return t.context[k] or t.template[k] or _ENV[k]
- end }
- loadchunk = function(view)
- return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
- end
- end
-end
-
-function template.caching(enable)
- if enable ~= nil then caching = enable == true end
- return caching
-end
-
-function template.output(s)
- if s == nil or s == null then return "" end
- if type(s) == "function" then return template.output(s()) end
- return tostring(s)
-end
-
-function template.escape(s, c)
- if type(s) == "string" then
- if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
- return gsub(s, "[\">/<'&]", HTML_ENTITIES)
- end
- return template.output(s)
-end
-
-function template.new(view, layout)
- assert(view, "view was not provided for template.new(view, layout).")
- local render, compile = template.render, template.compile
- if layout then
- if type(layout) == "table" then
- return setmetatable({ render = function(self, context)
- local context = context or self
- context.blocks = context.blocks or {}
- context.view = compile(view)(context)
- layout.blocks = context.blocks or {}
- layout.view = context.view or ""
- return layout:render()
- end }, { __tostring = function(self)
- local context = self
- context.blocks = context.blocks or {}
- context.view = compile(view)(context)
- layout.blocks = context.blocks or {}
- layout.view = context.view
- return tostring(layout)
- end })
- else
- return setmetatable({ render = function(self, context)
- local context = context or self
- context.blocks = context.blocks or {}
- context.view = compile(view)(context)
- return render(layout, context)
- end }, { __tostring = function(self)
- local context = self
- context.blocks = context.blocks or {}
- context.view = compile(view)(context)
- return compile(layout)(context)
- end })
- end
- end
- return setmetatable({ render = function(self, context)
- return render(view, context or self)
- end }, { __tostring = function(self)
- return compile(view)(self)
- end })
-end
-
-function template.precompile(view, path, strip)
- local chunk = dump(template.compile(view), strip ~= false)
- if path then
- local file = open(path, "wb")
- file:write(chunk)
- file:close()
- end
- return chunk
-end
-
-function template.compile(view, key, plain)
- assert(view, "view was not provided for template.compile(view, key, plain).")
- if key == "no-cache" then
- return loadchunk(template.parse(view, plain)), false
- end
- key = key or view
- local cache = template.cache
- if cache[key] then return cache[key], true end
- local func = loadchunk(template.parse(view, plain))
- if caching then cache[key] = func end
- return func, false
-end
-
-function template.parse(view, plain)
- assert(view, "view was not provided for template.parse(view, plain).")
- if not plain then
- view = template.load(view)
- if byte(view, 1, 1) == 27 then return view end
- end
- local j = 2
- local c = {[[
-context=... or {}
-local function include(v, c) return template.compile(v)(c or context) end
-local ___,blocks,layout={},blocks or {}
-]] }
- local i, s = 1, find(view, "{", 1, true)
- while s do
- local t, p = sub(view, s + 1, s + 1), s + 2
- if t == "{" then
- local e = find(view, "}}", p, true)
- if e then
- local z, w = escaped(view, s)
- if i < s - w then
- c[j] = "___[#___+1]=[=[\n"
- c[j+1] = sub(view, i, s - 1 - w)
- c[j+2] = "]=]\n"
- j=j+3
- end
- if z then
- i = s
- else
- c[j] = "___[#___+1]=template.escape("
- c[j+1] = trim(sub(view, p, e - 1))
- c[j+2] = ")\n"
- j=j+3
- s, i = e + 1, e + 2
- end
- end
- elseif t == "*" then
- local e = find(view, "*}", p, true)
- if e then
- local z, w = escaped(view, s)
- if i < s - w then
- c[j] = "___[#___+1]=[=[\n"
- c[j+1] = sub(view, i, s - 1 - w)
- c[j+2] = "]=]\n"
- j=j+3
- end
- if z then
- i = s
- else
- c[j] = "___[#___+1]=template.output("
- c[j+1] = trim(sub(view, p, e - 1))
- c[j+2] = ")\n"
- j=j+3
- s, i = e + 1, e + 2
- end
- end
- elseif t == "%" then
- local e = find(view, "%}", p, true)
- if e then
- local z, w = escaped(view, s)
- if z then
- if i < s - w then
- c[j] = "___[#___+1]=[=[\n"
- c[j+1] = sub(view, i, s - 1 - w)
- c[j+2] = "]=]\n"
- j=j+3
- end
- i = s
- else
- local n = e + 2
- if sub(view, n, n) == "\n" then
- n = n + 1
- end
- local r = rpos(view, s - 1)
- if i <= r then
- c[j] = "___[#___+1]=[=[\n"
- c[j+1] = sub(view, i, r)
- c[j+2] = "]=]\n"
- j=j+3
- end
- c[j] = trim(sub(view, p, e - 1))
- c[j+1] = "\n"
- j=j+2
- s, i = n - 1, n
- end
- end
- elseif t == "(" then
- local e = find(view, ")}", p, true)
- if e then
- local z, w = escaped(view, s)
- if i < s - w then
- c[j] = "___[#___+1]=[=[\n"
- c[j+1] = sub(view, i, s - 1 - w)
- c[j+2] = "]=]\n"
- j=j+3
- end
- if z then
- i = s
- else
- local f = sub(view, p, e - 1)
- local x = find(f, ",", 2, true)
- if x then
- c[j] = "___[#___+1]=include([=["
- c[j+1] = trim(sub(f, 1, x - 1))
- c[j+2] = "]=],"
- c[j+3] = trim(sub(f, x + 1))
- c[j+4] = ")\n"
- j=j+5
- else
- c[j] = "___[#___+1]=include([=["
- c[j+1] = trim(f)
- c[j+2] = "]=])\n"
- j=j+3
- end
- s, i = e + 1, e + 2
- end
- end
- elseif t == "[" then
- local e = find(view, "]}", p, true)
- if e then
- local z, w = escaped(view, s)
- if i < s - w then
- c[j] = "___[#___+1]=[=[\n"
- c[j+1] = sub(view, i, s - 1 - w)
- c[j+2] = "]=]\n"
- j=j+3
- end
- if z then
- i = s
- else
- c[j] = "___[#___+1]=include("
- c[j+1] = trim(sub(view, p, e - 1))
- c[j+2] = ")\n"
- j=j+3
- s, i = e + 1, e + 2
- end
- end
- elseif t == "-" then
- local e = find(view, "-}", p, true)
- if e then
- local x, y = find(view, sub(view, s, e + 1), e + 2, true)
- if x then
- local z, w = escaped(view, s)
- if z then
- if i < s - w then
- c[j] = "___[#___+1]=[=[\n"
- c[j+1] = sub(view, i, s - 1 - w)
- c[j+2] = "]=]\n"
- j=j+3
- end
- i = s
- else
- y = y + 1
- x = x - 1
- if sub(view, y, y) == "\n" then
- y = y + 1
- end
- local b = trim(sub(view, p, e - 1))
- if b == "verbatim" or b == "raw" then
- if i < s - w then
- c[j] = "___[#___+1]=[=[\n"
- c[j+1] = sub(view, i, s - 1 - w)
- c[j+2] = "]=]\n"
- j=j+3
- end
- c[j] = "___[#___+1]=[=["
- c[j+1] = sub(view, e + 2, x)
- c[j+2] = "]=]\n"
- j=j+3
- else
- if sub(view, x, x) == "\n" then
- x = x - 1
- end
- local r = rpos(view, s - 1)
- if i <= r then
- c[j] = "___[#___+1]=[=[\n"
- c[j+1] = sub(view, i, r)
- c[j+2] = "]=]\n"
- j=j+3
- end
- c[j] = 'blocks["'
- c[j+1] = b
- c[j+2] = '"]=include[=['
- c[j+3] = sub(view, e + 2, x)
- c[j+4] = "]=]\n"
- j=j+5
- end
- s, i = y - 1, y
- end
- end
- end
- elseif t == "#" then
- local e = find(view, "#}", p, true)
- if e then
- local z, w = escaped(view, s)
- if i < s - w then
- c[j] = "___[#___+1]=[=[\n"
- c[j+1] = sub(view, i, s - 1 - w)
- c[j+2] = "]=]\n"
- j=j+3
- end
- if z then
- i = s
- else
- e = e + 2
- if sub(view, e, e) == "\n" then
- e = e + 1
- end
- s, i = e - 1, e
- end
- end
- end
- s = find(view, "{", s + 1, true)
- end
- s = sub(view, i)
- if s and s ~= "" then
- c[j] = "___[#___+1]=[=[\n"
- c[j+1] = s
- c[j+2] = "]=]\n"
- j=j+3
- end
- c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)"
- return concat(c)
-end
-
-function template.render(view, context, key, plain)
- assert(view, "view was not provided for template.render(view, context, key, plain).")
- return template.print(template.compile(view, key, plain)(context))
-end
-
-return template
\ No newline at end of file
diff --git a/lualib/template/html.lua b/lualib/template/html.lua
new file mode 100644
index 00000000..5d97fe05
--- /dev/null
+++ b/lualib/template/html.lua
@@ -0,0 +1,53 @@
+local template = require "template"
+local escape = template.escape
+
+local setmetatable = setmetatable
+local concat = table.concat
+local pairs = pairs
+local type = type
+
+local function tag(name, content, attr)
+ local r, a = {}, {}
+ content = content or attr
+ r[#r + 1] = "<"
+ r[#r + 1] = name
+ if attr then
+ for k, v in pairs(attr) do
+ if type(k) == "number" then
+ a[#a + 1] = escape(v)
+ else
+ a[#a + 1] = k .. '="' .. escape(v) .. '"'
+ end
+ end
+ if #a > 0 then
+ r[#r + 1] = " "
+ r[#r + 1] = concat(a, " ")
+ end
+ end
+ if type(content) == "string" then
+ r[#r + 1] = ">"
+ r[#r + 1] = escape(content)
+ r[#r + 1] = ""
+ r[#r + 1] = name
+ r[#r + 1] = ">"
+ else
+ r[#r + 1] = " />"
+ end
+ return concat(r)
+end
+
+local html = { __index = function(_, name)
+ return function(attr)
+ if type(attr) == "table" then
+ return function(content)
+ return tag(name, content, attr)
+ end
+ else
+ return tag(name, attr)
+ end
+ end
+end }
+
+template.html = setmetatable(html, html)
+
+return template.html
\ No newline at end of file
diff --git a/lualib/template/init.lua b/lualib/template/init.lua
new file mode 100644
index 00000000..fa69cd1f
--- /dev/null
+++ b/lualib/template/init.lua
@@ -0,0 +1 @@
+return require "template.new".new(false)
\ No newline at end of file
diff --git a/lualib/template/new.lua b/lualib/template/new.lua
new file mode 100644
index 00000000..80a4f200
--- /dev/null
+++ b/lualib/template/new.lua
@@ -0,0 +1,612 @@
+local aio = require "aio"
+local aio_open = aio.open
+
+local sys = require "sys"
+local newtab = sys.new_tab
+
+local setmetatable = setmetatable
+local tostring = tostring
+local concat = table.concat
+local assert = assert
+local pcall = pcall
+local load = load
+local type = type
+local dump = string.dump
+local find = string.find
+local gsub = string.gsub
+local byte = string.byte
+local null = null
+local sub = string.sub
+local print_view = io.write
+
+local HTML_ENTITIES = {
+ ["&"] = "&",
+ ["<"] = "<",
+ [">"] = ">",
+ ['"'] = """,
+ ["'"] = "'",
+ ["/"] = "/"
+}
+
+local CODE_ENTITIES = {
+ ["{"] = "{",
+ ["}"] = "}",
+ ["&"] = "&",
+ ["<"] = "<",
+ [">"] = ">",
+ ['"'] = """,
+ ["'"] = "'",
+ ["/"] = "/"
+}
+
+local ESC = byte("\27")
+local NUL = byte("\0")
+local HT = byte("\t")
+local VT = byte("\v")
+local LF = byte("\n")
+local SOL = byte("/")
+local BSOL = byte("\\")
+local SP = byte(" ")
+local AST = byte("*")
+local NUM = byte("#")
+local LPAR = byte("(")
+local LSQB = byte("[")
+local LCUB = byte("{")
+local MINUS = byte("-")
+local PERCNT = byte("%")
+
+local EMPTY = ""
+
+local VIEW_ENV = { __index = function(t, k)
+ return t.context[k] or t.template[k] or _ENV[k]
+ end
+}
+
+local function enabled(val)
+ if val == nil then return true end
+ return val == true or (val == "1" or val == "true" or val == "on")
+end
+
+local function trim(s)
+ return gsub(gsub(s, "^%s+", EMPTY), "%s+$", EMPTY)
+end
+
+local function rpos(view, s)
+ while s > 0 do
+ local c = byte(view, s, s)
+ if c == SP or c == HT or c == VT or c == NUL then
+ s = s - 1
+ else
+ break
+ end
+ end
+ return s
+end
+
+local function escaped(view, s)
+ if s > 1 and byte(view, s - 1, s - 1) == BSOL then
+ if s > 2 and byte(view, s - 2, s - 2) == BSOL then
+ return false, 1
+ else
+ return true, 1
+ end
+ end
+ return false, 0
+end
+
+local function read_file(path)
+ local f, err = aio_open(path)
+ if not f then
+ return nil, err
+ end
+ local content = f:readall()
+ f:close()
+ f = nil
+ return content
+end
+
+local function load_view(template)
+ return function(view, plain)
+ if plain == true then
+ return view
+ end
+ local path, root = view, template.root
+ if root and root ~= EMPTY then
+ if byte(root, -1) == SOL then
+ root = sub(root, 1, -2)
+ end
+ if byte(view, 1) == SOL then
+ path = sub(view, 2)
+ end
+ path = root .. "/" .. path
+ end
+ return plain == false and assert(read_file(path)) or read_file(path) or view
+ end
+end
+
+local function load_file(func)
+ return function(view) return func(view, false) end
+end
+
+local function load_string(func)
+ return function(view) return func(view, true) end
+end
+
+local function loader(template)
+ return function(view)
+ return assert(load(view, nil, nil, setmetatable({ template = template }, VIEW_ENV)))
+ end
+end
+
+local function visit(visitors, content, tag, name)
+ if not visitors then
+ return content
+ end
+
+ for i = 1, visitors.n do
+ content = visitors[i](content, tag, name)
+ end
+
+ return content
+end
+
+local function new(template, safe)
+ template = template or newtab(0, 26)
+
+ template._VERSION = "2.0"
+ template.cache = {}
+ template.load = load_view(template)
+ template.load_file = load_file(template.load)
+ template.load_string = load_string(template.load)
+ template.print = print_view
+
+ local load_chunk = loader(template)
+
+ local caching = true
+
+ local visitors
+ function template.visit(func)
+ if not visitors then
+ visitors = { func, n = 1 }
+ return
+ end
+ visitors.n = visitors.n + 1
+ visitors[visitors.n] = func
+ end
+
+ function template.caching(enable)
+ if enable ~= nil then caching = enable == true end
+ return caching
+ end
+
+ function template.output(s)
+ if s == nil or s == null then return EMPTY end
+ if type(s) == "function" then return template.output(s()) end
+ return tostring(s)
+ end
+
+ function template.escape(s, c)
+ if type(s) == "string" then
+ if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
+ return gsub(s, "[\">/<'&]", HTML_ENTITIES)
+ end
+ return template.output(s)
+ end
+
+ function template.new(view, layout)
+
+ local vt = type(view)
+
+ if vt == "boolean" then
+ return new(nil, view)
+ end
+ if vt == "table" then
+ return new(view, safe)
+ end
+ if vt == "nil" then
+ return new(nil, safe)
+ end
+
+ local render
+ local process
+ if layout then
+ if type(layout) == "table" then
+ render = function(self, context)
+ context = context or self
+ context.blocks = context.blocks or {}
+ context.view = template.process(view, context)
+ layout.blocks = context.blocks or {}
+ layout.view = context.view or EMPTY
+ layout:render()
+ end
+ process = function(self, context)
+ context = context or self
+ context.blocks = context.blocks or {}
+ context.view = template.process(view, context)
+ layout.blocks = context.blocks or {}
+ layout.view = context.view
+ return tostring(layout)
+ end
+ else
+ render = function(self, context)
+ context = context or self
+ context.blocks = context.blocks or {}
+ context.view = template.process(view, context)
+ template.render(layout, context)
+ end
+ process = function(self, context)
+ context = context or self
+ context.blocks = context.blocks or {}
+ context.view = template.process(view, context)
+ return template.process(layout, context)
+ end
+ end
+ else
+ render = function(self, context)
+ return template.render(view, context or self)
+ end
+ process = function(self, context)
+ return template.process(view, context or self)
+ end
+ end
+
+ if safe then
+ return setmetatable({
+ render = function(...)
+ local ok, err = pcall(render, ...)
+ if not ok then
+ return nil, err
+ end
+ end,
+ process = function(...)
+ local ok, output = pcall(process, ...)
+ if not ok then
+ return nil, output
+ end
+ return output
+ end,
+ }, {
+ __tostring = function(...)
+ local ok, output = pcall(process, ...)
+ if not ok then
+ return ""
+ end
+ return output
+ end })
+ end
+
+ return setmetatable({
+ render = render,
+ process = process
+ }, {
+ __tostring = process
+ })
+ end
+
+ function template.precompile(view, path, strip, plain)
+ local chunk = dump(template.compile(view, nil, plain), strip ~= false)
+ if path then
+ local f = aio_open(path)
+ f:write(chunk)
+ f:close()
+ f = nil
+ end
+ return chunk
+ end
+
+ function template.precompile_string(view, path, strip)
+ return template.precompile(view, path, strip, true)
+ end
+
+ function template.precompile_file(view, path, strip)
+ return template.precompile(view, path, strip, false)
+ end
+
+ function template.compile(view, cache_key, plain)
+ assert(view, "view was not provided for template.compile(view, cache_key, plain)")
+ if cache_key == "no-cache" then
+ return load_chunk(template.parse(view, plain)), false
+ end
+ cache_key = cache_key or view
+ local cache = template.cache
+ if cache[cache_key] then return cache[cache_key], true end
+ local func = load_chunk(template.parse(view, plain))
+ if caching then cache[cache_key] = func end
+ return func, false
+ end
+
+ function template.compile_file(view, cache_key)
+ return template.compile(view, cache_key, false)
+ end
+
+ function template.compile_string(view, cache_key)
+ return template.compile(view, cache_key, true)
+ end
+
+ function template.parse(view, plain)
+ assert(view, "view was not provided for template.parse(view, plain)")
+ if plain ~= true then
+ view = template.load(view, plain)
+ if byte(view, 1, 1) == ESC then return view end
+ end
+ local j = 2
+ local c = {[[
+context=... or {}
+local ___,blocks,layout={},blocks or {}
+local function include(v, c) return template.process(v, c or context) end
+local function echo(...) for i=1,select("#", ...) do ___[#___+1] = tostring(select(i, ...)) end end
+]] }
+ local i, s = 1, find(view, "{", 1, true)
+ while s do
+ local t, p = byte(view, s + 1, s + 1), s + 2
+ if t == LCUB then
+ local e = find(view, "}}", p, true)
+ if e then
+ local z, w = escaped(view, s)
+ if i < s - w then
+ c[j] = "___[#___+1]=[=[\n"
+ c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
+ c[j+2] = "]=]\n"
+ j=j+3
+ end
+ if z then
+ i = s
+ else
+ c[j] = "___[#___+1]=template.escape("
+ c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "{")
+ c[j+2] = ")\n"
+ j=j+3
+ s, i = e + 1, e + 2
+ end
+ end
+ elseif t == AST then
+ local e = find(view, "*}", p, true)
+ if e then
+ local z, w = escaped(view, s)
+ if i < s - w then
+ c[j] = "___[#___+1]=[=[\n"
+ c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
+ c[j+2] = "]=]\n"
+ j=j+3
+ end
+ if z then
+ i = s
+ else
+ c[j] = "___[#___+1]=template.output("
+ c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "*")
+ c[j+2] = ")\n"
+ j=j+3
+ s, i = e + 1, e + 2
+ end
+ end
+ elseif t == PERCNT then
+ local e = find(view, "%}", p, true)
+ if e then
+ local z, w = escaped(view, s)
+ if z then
+ if i < s - w then
+ c[j] = "___[#___+1]=[=[\n"
+ c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
+ c[j+2] = "]=]\n"
+ j=j+3
+ end
+ i = s
+ else
+ local n = e + 2
+ if byte(view, n, n) == LF then
+ n = n + 1
+ end
+ local r = rpos(view, s - 1)
+ if i <= r then
+ c[j] = "___[#___+1]=[=[\n"
+ c[j+1] = visit(visitors, sub(view, i, r))
+ c[j+2] = "]=]\n"
+ j=j+3
+ end
+ c[j] = visit(visitors, trim(sub(view, p, e - 1)), "%")
+ c[j+1] = "\n"
+ j=j+2
+ s, i = n - 1, n
+ end
+ end
+ elseif t == LPAR then
+ local e = find(view, ")}", p, true)
+ if e then
+ local z, w = escaped(view, s)
+ if i < s - w then
+ c[j] = "___[#___+1]=[=[\n"
+ c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
+ c[j+2] = "]=]\n"
+ j=j+3
+ end
+ if z then
+ i = s
+ else
+ local f = visit(visitors, sub(view, p, e - 1), "(")
+ local x = find(f, ",", 2, true)
+ if x then
+ c[j] = "___[#___+1]=include([=["
+ c[j+1] = trim(sub(f, 1, x - 1))
+ c[j+2] = "]=],"
+ c[j+3] = trim(sub(f, x + 1))
+ c[j+4] = ")\n"
+ j=j+5
+ else
+ c[j] = "___[#___+1]=include([=["
+ c[j+1] = trim(f)
+ c[j+2] = "]=])\n"
+ j=j+3
+ end
+ s, i = e + 1, e + 2
+ end
+ end
+ elseif t == LSQB then
+ local e = find(view, "]}", p, true)
+ if e then
+ local z, w = escaped(view, s)
+ if i < s - w then
+ c[j] = "___[#___+1]=[=[\n"
+ c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
+ c[j+2] = "]=]\n"
+ j=j+3
+ end
+ if z then
+ i = s
+ else
+ c[j] = "___[#___+1]=include("
+ c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "[")
+ c[j+2] = ")\n"
+ j=j+3
+ s, i = e + 1, e + 2
+ end
+ end
+ elseif t == MINUS then
+ local e = find(view, "-}", p, true)
+ if e then
+ local x, y = find(view, sub(view, s, e + 1), e + 2, true)
+ if x then
+ local z, w = escaped(view, s)
+ if z then
+ if i < s - w then
+ c[j] = "___[#___+1]=[=[\n"
+ c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
+ c[j+2] = "]=]\n"
+ j=j+3
+ end
+ i = s
+ else
+ y = y + 1
+ x = x - 1
+ if byte(view, y, y) == LF then
+ y = y + 1
+ end
+ local b = trim(sub(view, p, e - 1))
+ if b == "verbatim" or b == "raw" then
+ if i < s - w then
+ c[j] = "___[#___+1]=[=[\n"
+ c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
+ c[j+2] = "]=]\n"
+ j=j+3
+ end
+ c[j] = "___[#___+1]=[=["
+ c[j+1] = visit(visitors, sub(view, e + 2, x))
+ c[j+2] = "]=]\n"
+ j=j+3
+ else
+ if byte(view, x, x) == LF then
+ x = x - 1
+ end
+ local r = rpos(view, s - 1)
+ if i <= r then
+ c[j] = "___[#___+1]=[=[\n"
+ c[j+1] = visit(visitors, sub(view, i, r))
+ c[j+2] = "]=]\n"
+ j=j+3
+ end
+ c[j] = 'blocks["'
+ c[j+1] = b
+ c[j+2] = '"]=include[=['
+ c[j+3] = visit(visitors, sub(view, e + 2, x), "-", b)
+ c[j+4] = "]=]\n"
+ j=j+5
+ end
+ s, i = y - 1, y
+ end
+ end
+ end
+ elseif t == NUM then
+ local e = find(view, "#}", p, true)
+ if e then
+ local z, w = escaped(view, s)
+ if i < s - w then
+ c[j] = "___[#___+1]=[=[\n"
+ c[j+1] = visit(visitors, sub(view, i, s - 1 - w))
+ c[j+2] = "]=]\n"
+ j=j+3
+ end
+ if z then
+ i = s
+ else
+ e = e + 2
+ if byte(view, e, e) == LF then
+ e = e + 1
+ end
+ s, i = e - 1, e
+ end
+ end
+ end
+ s = find(view, "{", s + 1, true)
+ end
+ s = sub(view, i)
+ if s and s ~= EMPTY then
+ c[j] = "___[#___+1]=[=[\n"
+ c[j+1] = visit(visitors, s)
+ c[j+2] = "]=]\n"
+ j=j+3
+ end
+ c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)" -- luacheck: ignore
+ return concat(c)
+ end
+
+ function template.parse_file(view)
+ return template.parse(view, false)
+ end
+
+ function template.parse_string(view)
+ return template.parse(view, true)
+ end
+
+ function template.process(view, context, cache_key, plain)
+ assert(view, "view was not provided for template.process(view, context, cache_key, plain)")
+ return template.compile(view, cache_key, plain)(context)
+ end
+
+ function template.process_file(view, context, cache_key)
+ assert(view, "view was not provided for template.process_file(view, context, cache_key)")
+ return template.compile(view, cache_key, false)(context)
+ end
+
+ function template.process_string(view, context, cache_key)
+ assert(view, "view was not provided for template.process_string(view, context, cache_key)")
+ return template.compile(view, cache_key, true)(context)
+ end
+
+ function template.render(view, context, cache_key, plain)
+ assert(view, "view was not provided for template.render(view, context, cache_key, plain)")
+ template.print(template.process(view, context, cache_key, plain))
+ end
+
+ function template.render_file(view, context, cache_key)
+ assert(view, "view was not provided for template.render_file(view, context, cache_key)")
+ template.render(view, context, cache_key, false)
+ end
+
+ function template.render_string(view, context, cache_key)
+ assert(view, "view was not provided for template.render_string(view, context, cache_key)")
+ template.render(view, context, cache_key, true)
+ end
+
+ if safe then
+ return setmetatable({}, {
+ __index = function(_, k)
+ if type(template[k]) == "function" then
+ return function(...)
+ local ok, a, b = pcall(template[k], ...)
+ if not ok then
+ return nil, a
+ end
+ return a, b
+ end
+ end
+ return template[k]
+ end,
+ __new_index = function(_, k, v)
+ template[k] = v
+ end,
+ })
+ end
+
+ return template
+end
+
+return new()
\ No newline at end of file
diff --git a/lualib/uitls/init.lua b/lualib/uitls/init.lua
deleted file mode 100644
index 0857f3ee..00000000
--- a/lualib/uitls/init.lua
+++ /dev/null
@@ -1,74 +0,0 @@
--- 格式化输出(美化)
-var_dump = function (data, showMetatable, lastCount)
- if type(data) ~= "table" then
- --Value
- if type(data) == "string" then
- io.write('"', data, '"')
- else
- io.write(tostring(data))
- end
- else
- --Format
- local count = lastCount or 0
- count = count + 1
- io.write("{\n")
- --Metatable
- if showMetatable then
- for i = 1,count do io.write(" ") end
- local mt = getmetatable(data)
- io.write("\"__metatable\" = ")
- var_dump(mt, showMetatable, count) -- 如果不想看到元表的元表,可将showMetatable处填nil
- io.write(",\n") --如果不想在元表后加逗号,可以删除这里的逗号
- end
- --Key
- for key,value in pairs(data) do
- for i = 1,count do io.write(" ") end
- if type(key) == "string" then
- -- io.write("\"", key, "\" = ")
- io.write('["', key, '"] = ')
- elseif type(key) == "number" then
- io.write("[", key, "] = ")
- else
- io.write(tostring(key))
- end
- var_dump(value, showMetatable, count) -- 如果不想看到子table的元表,可将showMetatable处填nil
- io.write(",\n") --如果不想在table的每一个item后加逗号,可以删除这里的逗号
- end
- --Format
- for i = 1, lastCount or 0 do io.write(" ") end
- io.write("}")
- end
- --Format
- if not lastCount then
- io.write("\n")
- end
-end
-
--- local co = require "internal.Co"
--- local tcp = require "internal.TCP"
--- local Timer = require "internal.Timer"
--- local DB = require "DB"
--- local Cache = require "Cache"
--- co.spwan(function ( ... )
--- while 1 do
--- local self = co.self()
--- local ti = Timer.timeout(0.1, function()
--- local co_count, task_count = co.count()
--- local tcp_count = tcp.count()
--- local time_count = Timer.count()
--- local db_count = DB.count()
--- local cache_count = Cache.count()
--- print("=======================")
--- print("co 数量为:", co_count)
--- print("tcp 数量为:", tcp_count)
--- print("task 数量为:", task_count)
--- print("timer 数量为:", time_count)
--- print("db 数量为:", db_count)
--- print("cache 数量为:", cache_count)
--- print("当前内存为:", collectgarbage('count'))
--- print("=======================")
--- co.wakeup(self)
--- end)
--- co.wait()
--- end
--- end)
diff --git a/lualib/url/init.lua b/lualib/url/init.lua
new file mode 100644
index 00000000..a519bea2
--- /dev/null
+++ b/lualib/url/init.lua
@@ -0,0 +1,126 @@
+local type = type
+local assert = assert
+local setmetatable = setmetatable
+
+local strsub = string.sub
+local strbyte = string.byte
+local strfind = string.find
+local strfmt = string.format
+local strmatch = string.match
+
+local tconcat = table.concat
+
+-- C版实现
+local encode = require("crypt").urlencode
+local decode = require("crypt").urldecode
+
+--[[
+经过测试: 100万此编码/解码两者性能相差30倍, 正好是lua与C的性能差距.
+]]
+
+local url = {}
+
+-- urlencode编码
+function url.encode(s)
+ -- return spliter(spliter(s, "([^%w%.%- ])", function(c) return fmt("%%%02X", byte(c)) end), " ", "+")
+ return encode(s)
+end
+
+-- urldecode解码
+function url.decode(s)
+ -- return spliter(s, '%%(%x%x)', function(h) return char(tonumber(h, 16)) end)
+ return decode(s)
+end
+
+local meta = {}
+
+function meta.__tostring(t)
+ return strfmt(
+ "Url(sheme='%s', netloc='%s', path='%s', query='%s', fragment='%s')",
+ t.sheme or '', t.netloc or '', t.path or '', t.query or '', t.fragment or ''
+ )
+end
+
+local function parse_other(other, t, dec)
+ -- find '#' or '?'
+ local pos = strfind(other, "#")
+ if pos then
+ -- Begin with '#'
+ t.fragment = strsub(other, pos + 1)
+ if pos > 1 then
+ t.query = strsub(other, 2, pos - 1)
+ end
+ else
+ -- got query string.
+ pos = strfind(other, "?")
+ if pos then
+ t.query = strsub(other, pos + 1)
+ end
+ end
+ if dec and t.query and t.query ~= '' then
+ t.query = decode(t.query)
+ end
+end
+
+local function parse_noloc(str, dec)
+ local t, other = {}, nil
+ t.path, other = strmatch(str, "([^%?#]*)([%?#]?.*)")
+ parse_other(other, t, dec)
+ return setmetatable(t, meta)
+end
+
+local function parse_nosheme(str, dec)
+ local t, other = {}, nil
+ -- got url split string.
+ t.path, other = strmatch(str, "([^%?#]*)([%?#]?.*)")
+ if strbyte(t.path) == 47 then
+ return parse_noloc(str)
+ end
+ if strfind(t.path, '/') then
+ t.netloc, t.path, other = strmatch(str, "([^/]*)([^%?#]*)([%?#]?.*)")
+ end
+ parse_other(other, t, dec)
+ return setmetatable(t, meta)
+end
+
+---comment split url to Url Table(Class).
+---@param str string @Url buffer.
+---@param dec boolean @Url decode.
+---@return table
+function url.split(str, dec)
+ assert(type(str) == 'string', 'Invalid Url type.')
+ local t, other = {}, nil
+ -- got url split string.
+ t.sheme, t.netloc, t.path, other = strmatch(str, '([^:]*)[:]?//([^/]*)([/]?[^%?#]*)([%?#]?.*)')
+ if not t.sheme then
+ return parse_nosheme(str, dec)
+ end
+ parse_other(other, t, dec)
+ return setmetatable(t, meta)
+end
+
+---comment Use `Url` Table(Class) to Build url `String`.
+---@param tab table @Split Class.
+---@return string
+function url.join(tab)
+ assert(type(tab) == 'table', 'Invalid Url Class.')
+ local urls = {}
+ if tab.sheme then
+ urls[1] = (tab.sheme ~= '' and (tab.sheme .. ':') or '' ) .. '//'
+ end
+ if tab.netloc then
+ urls[#urls+1] = tab.netloc
+ end
+ if tab.path then
+ urls[#urls+1] = tab.path
+ end
+ if tab.query then
+ urls[#urls+1] = '?' .. tab.query
+ end
+ if tab.fragment then
+ urls[#urls+1] = '#' .. tab.fragment
+ end
+ return tconcat(urls)
+end
+
+return url
diff --git a/lualib/utils/init.lua b/lualib/utils/init.lua
new file mode 100644
index 00000000..3b8e5a66
--- /dev/null
+++ b/lualib/utils/init.lua
@@ -0,0 +1,133 @@
+local type = type
+local pairs = pairs
+local print = print
+local tostring = tostring
+
+local getmetatable = getmetatable
+local setmetatable = setmetatable
+
+local mtype = math.type or function (v)
+ return type(v) == 'number' and
+ (v % 1 == 0 and 'integer' or 'float') or 'nil'
+end
+
+local debug_getinfo = debug.getinfo
+
+local strrep = string.rep
+local strfmt = string.format
+
+local tconcat = table.concat
+local tinsert = table.insert
+
+local r = debug.getregistry()
+local l, g = r._LOADED, _G
+
+local V = tonumber(_VERSION:sub(4))
+
+local function space(level)
+ return strrep(' ', level)
+end
+
+local function tokey(key)
+ if type(key) ~= 'string' then
+ return strfmt("%s", key)
+ end
+ return strfmt("%q", key)
+end
+
+local array_mt = r['Lua.Array']
+local function isarray(tab)
+ if #tab > 0 then
+ return true
+ end
+ return array_mt and getmetatable(tab) == array_mt
+end
+
+---comment 格式化打印变量`tab`.
+---@param tab any @指定指定变量
+---@param meta boolean? @是否跟进元表
+---@param level integer? @指定打印层级
+local function var_dump(tab, meta, level)
+ if type(tab) ~= 'table' then
+ return strfmt("%s\n", tostring(tab))
+ end
+ local ptab = {}
+ -- 拆分
+ local M, I
+ if meta then
+ M = getmetatable(tab)
+ if M then
+ setmetatable(tab, nil)
+ tab.__metatable__ = M
+ end
+ end
+ if tab.__index == tab then
+ I = tab.__index
+ tab.__index = nil
+ end
+ local n = 0
+ local olevel = level
+ level = level + 1
+ for k, v in pairs(tab) do
+ if type(k) ~= 'number' and type(k) ~= 'string' then
+ k = tostring(k)
+ end
+ if type(v) == "number" then
+ if mtype(v) == 'float' then
+ tinsert(ptab, strfmt('%s[%s] = Number(%s),\n', space(level), tokey(k), v))
+ else
+ tinsert(ptab, strfmt('%s[%s] = Integer(%d),\n', space(level), tokey(k), v))
+ end
+ elseif type(v) == 'boolean' then
+ tinsert(ptab, strfmt('%s[%s] = Boolean(%s),\n', space(level), tokey(k), v))
+ elseif type(v) == 'table' then
+ if v == g or v == l then
+ tinsert(ptab, strfmt('%s[%s] = %s,\n', space(level), tokey(k), tostring(v)))
+ else
+ tinsert(ptab, strfmt('%s[%s] = %s', space(level), tokey(k), var_dump(v, meta, level)))
+ end
+ elseif type(v) == 'string' then
+ tinsert(ptab, strfmt('%s[%s] = String(%q),\n', space(level), tokey(k), v))
+ elseif type(v) == 'function' then
+ local info = debug_getinfo(v)
+ if info.linedefined > 0 then
+ if V > 5.3 then
+ tinsert(ptab, strfmt('%s[%s] = LuaFunction(%p%s),\n', space(level), tokey(k), v, info.source .. ':' .. info.linedefined))
+ else
+ tinsert(ptab, strfmt('%s[%s] = %s(%s),\n', space(level), tokey(k), v, info.source .. ':' .. info.linedefined))
+ end
+ else
+ if V > 5.3 then
+ tinsert(ptab, strfmt('%s[%s] = LuaCFunction(%p),\n', space(level), tokey(k), tostring(v)))
+ else
+ tinsert(ptab, strfmt('%s[%s] = c%s,\n', space(level), tokey(k), tostring(v)))
+ end
+ end
+ else
+ tinsert(ptab, strfmt('%s[%s] = %s,\n', space(level), tokey(k), tostring(v)))
+ end
+ n = n + 1
+ end
+ -- 还原
+ if meta then
+ if M then
+ setmetatable(tab, M)
+ tab.__metatable__ = nil
+ end
+ end
+ if I then
+ tab.__index = I
+ end
+ local left, right = "{\n", "%s}%s\n"
+ if n == #tab and isarray(tab) then
+ left, right = "[\n", "%s]%s\n"
+ end
+ return left .. tconcat(ptab) .. strfmt(right, space(olevel), olevel == 0 and "" or ",")
+end
+
+---comment Dump表结构
+---@param tab any @格式化打印当前表结构
+---@param meta? boolean @将元表结构也打印出来
+_G.var_dump = function (tab, meta)
+ print(var_dump(tab, meta, 0))
+end
\ No newline at end of file
diff --git a/lualib/utils/random.lua b/lualib/utils/random.lua
new file mode 100644
index 00000000..7829c461
--- /dev/null
+++ b/lualib/utils/random.lua
@@ -0,0 +1,173 @@
+local type = type
+local assert = assert
+
+local ssub = string.sub
+local char = string.char
+
+local mlog = math.log
+local mexp = math.exp
+local msqrt = math.sqrt
+local mcos = math.cos
+local msin = math.sin
+local mrandom = math.random
+local mrandomseed = math.randomseed
+
+local NV_MAGIC = 4 * mexp(-0.5) / msqrt(2.0)
+
+local PI2 = 2 * math.pi
+local gauss_next = nil
+
+local random = {}
+
+---comment @同`math.randomseed`
+function random.randomseed(...)
+ return mrandomseed(...)
+end
+
+---comment @同`math.random`
+---@return number
+function random.random(...)
+ return mrandom(...)
+end
+
+---comment 生成指定数量的字符数组
+---@param num integer @样品数量
+---@return table
+function random.generatechar(num)
+ local list = {}
+ for i = 1, num do
+ list[i] = char(mrandom(0, 255))
+ end
+ return list
+end
+
+---comment 生成指定数量的随机整数数组
+---@param x integer @随机数的最小值, 结果包含该值.
+---@param y integer @随机数的最大值, 结果包含该值.
+---@param num integer @样品数量
+---@return table
+function random.generateint (x, y, num)
+ local list = {}
+ for i = 1, num do
+ list[i] = mrandom(x, y)
+ end
+ return list
+end
+
+---comment 生成指定数量的随机实数数组
+---@param x integer @随机数的最小值, 结果包含该值.
+---@param y integer @随机数的最大值, 结果包含该值.
+---@param num integer @样品数量
+---@return table
+function random.generatefloat (x, y, num)
+ local list = {}
+ for i = 1, num do
+ list[i] = random.uniform(x, y)
+ end
+ return list
+end
+
+---comment 将随机生成一个实数,它在`x`, `y`范围内。
+---@param x integer @随机数的最小值, 结果包含该值.
+---@param y integer @随机数的最大值, 结果包含该值.
+---@return number
+function random.uniform(x, y)
+ return mrandom(x or 0, y or 4294967296) + mrandom()
+end
+
+---comment 返回给定`sequence`内的随机项。
+---@param sequence string | table @可以是`数组`或`字符串`.
+---@return any
+function random.choice(sequence)
+ local tp = type(sequence)
+ if tp ~= 'string' and tp ~= 'table' then
+ return
+ end
+ local len = #sequence
+ if len < 1 then
+ return
+ end
+ local rv = mrandom(1, len)
+ if tp == 'table' then
+ return sequence[rv]
+ end
+ return ssub(sequence, rv, rv)
+end
+
+---comment 打乱给定`sequence`内的顺序
+---@param sequence table @数组结构
+function random.shuffle(sequence)
+ local len = #assert(sequence, "Invalid `sequence`")
+ for i = 1, len, 1 do
+ local j = mrandom(1, len)
+ sequence[i], sequence[j] = sequence[j], sequence[i]
+ end
+ return sequence
+end
+
+---comment 根据给定`sequence`选取`num`个样品
+---@param sequence table @样品数组
+---@param num integer @样品数量
+function random.sample(sequence, num)
+ local len = #assert(sequence, "Invalid `sequence`")
+ assert(num and num <= len, "Invalid `num` or `sample` larger than population.")
+ local idx_list = {}
+ for i = 1, len do
+ idx_list[i] = i
+ end
+ random.shuffle(idx_list)
+ local list = {}
+ for i = 1, num do
+ list[i] = sequence[idx_list[i]]
+ end
+ return list
+end
+
+---comment 正态分布
+---@param mean number @平均值
+---@param sigma number @标准差
+---@return number
+function random.normalvariate(mean, sigma)
+ local z, zz, u1, u2
+ while true do
+ u1 = mrandom()
+ u2 = 1.0 - mrandom()
+ z = NV_MAGIC * (u1 - 0.5) / u2
+ zz = z * z / 4.0
+ if zz <= -mlog(u2) then
+ break
+ end
+ end
+ return mean + z * sigma
+end
+
+---comment 对数正态分布
+---@param mean number @平均值
+---@param sigma number @标准差
+---@return number
+function random.lognormvariate(mean, sigma)
+ return mexp(random.normalvariate(mean, sigma))
+end
+
+---comment 指数分布
+---@param lambd number @lambd 是1.0除以所需的平均值.
+function random.expovariate(lambd)
+ return -mlog(1.0 - mrandom()) / lambd
+end
+
+---comment 高斯分布
+---@param mean number @平均值
+---@param sigma number @标准差
+function random.gauss(mean, sigma)
+ local z = gauss_next
+ gauss_next = nil
+ if not z then
+ local x2pi = mrandom() * PI2
+ local g2rad = msqrt(-2.0 * mlog(1.0 - mrandom()))
+ z = mcos(x2pi) * g2rad
+ gauss_next = msin(x2pi) * g2rad
+ end
+ return mean + z * sigma
+end
+
+return random
\ No newline at end of file
diff --git a/lualib/utils/set.lua b/lualib/utils/set.lua
new file mode 100644
index 00000000..f1f909fe
--- /dev/null
+++ b/lualib/utils/set.lua
@@ -0,0 +1,136 @@
+local type = type
+local pairs = pairs
+local assert = assert
+local tostring = tostring
+local setmetatable = setmetatable
+
+local tconcat = table.concat
+
+---@class Set @集合
+local MetaSet = { map = {} }
+
+MetaSet.__index = MetaSet
+
+---comment 实例化`Set`
+---@return Set
+function MetaSet:new()
+ return setmetatable({ count = 0, map = {} }, MetaSet)
+end
+
+---comment 插入元素
+---@param value any @待插入的元素
+---@return boolean @已存在返回`false`, 否则返回`true`
+function MetaSet:insert(value)
+ if not self.map[value] then
+ self.map[value] = true
+ self.count = self.count + 1
+ return true
+ end
+ return false
+end
+
+---comment 删除元素
+---@param value any @待删除的元素
+---@return boolean @删除成功返回`true`, 否则返回`false`
+function MetaSet:remove(value)
+ if self.map[value] then
+ self.map[value] = nil
+ self.count = self.count - 1
+ return true
+ end
+ return false
+end
+
+---comment 返回集合内的元素数量
+---@return integer
+function MetaSet:len()
+ return self.count
+end
+
+---comment 是否为空
+---@return boolean
+function MetaSet:is_empty()
+ return self.count == 0
+end
+
+---comment 美化打印输出
+---@return string
+function MetaSet:__tostring()
+ local tab = {}
+ for element in pairs(self.map) do
+ tab[#tab+1] = tostring(element)
+ end
+ return "Set([" .. tconcat(tab, ', ') .. "])"
+end
+
+---comment 求差集
+---@param t1 Set @集合1
+---@param t2 Set @集合2
+---@return Set @新集合
+function MetaSet.__sub(t1, t2)
+ assert(type(t1) == 'table' and type(t2) == 'table', "[Set ERROR]: Invalid Set OP.")
+ local t1_map, t2_map = t1.map, t2.map
+ assert(type(t1_map) == 'table' and type(t2_map) == 'table', "[Set ERROR]: Invalid Set OP.")
+ local Set = MetaSet.new()
+ for k in pairs(t1_map) do
+ if not t2_map[k] then
+ Set:insert(k)
+ end
+ end
+ return Set
+end
+
+---comment 求交集
+---@param t1 Set @集合1
+---@param t2 Set @集合2
+---@return Set @新集合
+function MetaSet.__band (t1, t2)
+ assert(type(t1) == 'table' and type(t2) == 'table', "[Set ERROR]: Invalid Set OP.")
+ local t1_map, t2_map = t1.map, t2.map
+ assert(type(t1_map) == 'table' and type(t2_map) == 'table', "[Set ERROR]: Invalid Set OP.")
+ local Set = MetaSet.new()
+ for k in pairs(t1_map) do
+ if t2_map[k] then
+ Set:insert(k)
+ end
+ end
+ return Set
+end
+
+---comment 求并集
+---@param t1 Set @集合1
+---@param t2 Set @集合2
+---@return Set @新集合
+function MetaSet.__bor (t1, t2)
+ assert(type(t1) == 'table' and type(t2) == 'table', "[Set ERROR]: Invalid Set OP.")
+ local t1_map, t2_map = t1.map, t2.map
+ assert(type(t1_map) == 'table' and type(t2_map) == 'table', "[Set ERROR]: Invalid Set OP.")
+ local Set = MetaSet.new()
+ for k in pairs(t1_map) do
+ Set:insert(k)
+ end
+ for k in pairs(t2_map) do
+ Set:insert(k)
+ end
+ return Set
+end
+
+---comment 求并集
+---@param t1 Set @集合1
+---@param t2 Set @集合2
+---@return Set @新集合
+function MetaSet.__add (t1, t2)
+ assert(type(t1) == 'table' and type(t2) == 'table', "[Set ERROR]: Invalid Set OP.")
+ local t1_map, t2_map = t1.map, t2.map
+ assert(type(t1_map) == 'table' and type(t2_map) == 'table', "[Set ERROR]: Invalid Set OP.")
+ local Set = MetaSet.new()
+ for k in pairs(t1_map) do
+ Set:insert(k)
+ end
+ for k in pairs(t2_map) do
+ Set:insert(k)
+ end
+ return Set
+end
+
+return MetaSet
\ No newline at end of file
diff --git a/lualib/utils/string.lua b/lualib/utils/string.lua
new file mode 100644
index 00000000..0e5f65b9
--- /dev/null
+++ b/lualib/utils/string.lua
@@ -0,0 +1,216 @@
+local type = type
+local select = select
+local assert = assert
+local tonumber = tonumber
+
+local string = string
+local ssub = string.sub
+local srep = string.rep
+local sfind = string.find
+local sbyte = string.byte
+local sgsub = string.gsub
+local sgmatch = string.gmatch
+
+local mtype = math.type
+local tconcat = table.concat
+
+---comment 计算`pattern`在`text`中pos位置开始出现的总次数.
+---@param text string @实际内容
+---@param pattern string @匹配内容
+---@param pos integer @字符串
+---@return string @返回拼接好的字符串
+function string.count (text, pattern, pos)
+ if mtype(pos) ~= 'integer' or pos < 1 then
+ pos = 1
+ end
+ local count = 0
+ while true do
+ pos = sfind(text, pattern, pos)
+ if not pos then
+ break
+ end
+ count = count + 1
+ pos = pos + 1
+ end
+ return count
+end
+
+---comment 以`text`为中心拼接`count`个`fill`在两侧.
+---@param text string @实际内容
+---@param fill string @填充字符
+---@param count integer @填充数量
+---@return string @返回拼接好的字符串
+function string.center (text, fill, count)
+ assert(type(text) == 'string' and type(fill) == 'string' , "Invalid string `text` or `fill`.")
+ assert(type(count) == 'number' and tonumber(count) or count > 0, "Invalid fill `count`")
+ local fill_text = srep(fill, count)
+ return tconcat{ fill_text, text, fill_text }
+end
+
+---comment 判断指定字符串内容是否全部空格
+---@param text string @判断内容
+---@return boolean @如果`text`全是空格返回`true`, 否则返回`false`.
+function string.allspace (text)
+ return sfind(text or '', "^[ ]+$") and true or false
+end
+
+---comment 连接`1`个或`N`个字符串, 非可转换字符串的对象会抛出异常
+---@return string
+function string.join (...)
+ if select("#", ...) <= 1 then
+ return (...) or ''
+ end
+ return tconcat{...}
+end
+
+---comment 根据`sep`分割`text`字符串.
+---@param text string @待分割的字符串
+---@param sep string @分割用的分隔符
+---@return table @分割后的数组
+function string.split(text, sep)
+ assert(type(text) == 'string', "Invalid string `text`.")
+ if not sep or type(sep) ~= 'string' or sep == '' then
+ sep = '%,'
+ end
+ local index = 1
+ local list = {}
+ for sub in sgmatch(text, "([^" .. sep .. "]*)") do
+ list[index] = sub
+ index = index + 1
+ end
+ if index == 1 then
+ list[index] = text
+ end
+ return list
+end
+
+local function strip(text, sep, left, right)
+ if left then
+ text = sgsub(text, "^[" .. sep .."]+", "", 1)
+ end
+ if right then
+ text = sgsub(text, "[" .. sep .."]+$", "", 1)
+ end
+ return text
+end
+
+---comment 移除字符串头、尾指定的字符
+---@param text string @待分割的字符串
+---@param sep string @移除用的分隔符
+function string.strip (text, sep)
+ assert(type(text) == 'string', "Invalid string `text`.")
+ if not sep or type(sep) ~= 'string' or sep == '' then
+ sep = ' '
+ end
+ return strip(text, sep, true, true)
+end
+
+---comment 移除字符串头部指定的字符
+---@param text string @待分割的字符串
+---@param sep string @移除用的分隔符
+function string.lstrip (text, sep)
+ assert(type(text) == 'string', "Invalid string `text`.")
+ if not sep or type(sep) ~= 'string' or sep == '' then
+ sep = ' '
+ end
+ return strip(text, sep, true, false)
+end
+
+---comment 移除字符串尾部指定的字符
+---@param text string @待分割的字符串
+---@param sep string @移除用的分隔符
+function string.rstrip (text, sep)
+ assert(type(text) == 'string', "Invalid string `text`.")
+ if not sep or type(sep) ~= 'string' or sep == '' then
+ sep = ' '
+ end
+ return strip(text, sep, false, true)
+end
+
+---comment 判断起始位置是否指定内容
+---@param text string @待匹配内容
+---@param sstring string @其实内容
+---@param start integer @起始位置
+function string.startswith(text, sstring, start)
+ assert(type(text) == 'string', "Invalid string `text`.")
+ assert(type(sstring) == 'string', "Invalid start string.")
+ return sfind(text, '^' .. sstring, start) and true or false
+end
+
+---comment 判断结束位置是否指定内容
+---@param text string @待匹配内容
+---@param estring string @结束内容
+---@param over integer @结束位置
+function string.endswith(text, estring, over)
+ assert(type(text) == 'string', "Invalid string `text`.")
+ assert(type(estring) == 'string', "Invalid end string.")
+ return sfind(text, estring .. '$', over) and true or false
+end
+
+---comment 将`text`里的`s1`替换为`s2`(最多`count`此)
+---@param text string @原始内容
+---@param s1 string @匹配字符串
+---@param s2 string @替换字符串
+---@param count number @替换次数
+---@return string @替换后内容
+function string.replace (text, s1, s2, count)
+ assert(type(text) == 'string' and text ~= '', "Invalid text string.")
+ assert(type(s1) == 'string' and s1 ~= '', "Invalid s1 string.")
+ assert(type(s2) == 'string' and s2 ~= '', "Invalid s2 string.")
+ local s = sgsub(text, s1, s2, count)
+ return s
+end
+
+---comment 字符串转换为字节数组
+---@param text string @待转换的字符串
+---@return table @转换后的字节数组
+function string.tobytes(text)
+ assert(type(text) == 'string' and text ~= '', "Invalid text string.")
+ local list = {}
+ for idx = 1, #text do
+ list[#list+1] = sbyte(text, idx)
+ end
+ return list
+end
+
+---comment 向指定位置的字符串后插入字符串.
+---@param text string @原始字符串
+---@param pos integer @待插入的位置
+---@param str string @待插入的字符串
+---@return string @返回插入后的字符串内容
+function string.insert(text, pos, str)
+ assert(type(text) == 'string' and text ~= '', "Invalid text string.")
+ assert(type(pos) == 'number', "Invalid pos integer.")
+ assert(type(str) == 'string' and str ~= '', "Invalid text string.")
+ return tconcat{ssub(text, 1, pos), str, ssub(text, pos + 1, -1)}
+end
+
+local _, liconv = pcall(require, "liconv")
+
+---comment 替换iconv函数, 错误的实现会出现运行时错误.
+---@param module table
+---@param encode string @需保证`module[encode]`行为与`liconv.to`行为一致
+---@param decode string @需保证`module[decode]`行为与`liconv.from`行为一致
+function string.iconv(module, encode, decode)
+ liconv = { to = module[encode], from = module[decode] }
+end
+
+---comment 使用iconv进行编码转换
+---@param text string @文本内容
+---@param encoding string @目标编码
+---@return string @转换后的文本
+function string.encode (text, encoding)
+ assert(type(text) == 'string', "Invalid string `text`.")
+ assert(type(encoding) == 'string', "Invalid string encoding.")
+ return assert(type(liconv) == 'table' and liconv, "Lua iconv is not supported.").to(encoding, text)
+end
+
+---comment 使用iconv进行编码转换
+---@param text string @文本内容
+---@param decoding string @原始编码
+---@return string @转换后的文本
+function string.decode (text, decoding)
+ assert(type(text) == 'string', "Invalid string `text`.")
+ assert(type(decoding) == 'string', "Invalid string encoding.")
+ return assert(type(liconv) == 'table' and liconv, "Lua iconv is not supported.").from(decoding, text)
+end
\ No newline at end of file
diff --git a/lualib/utils/table.lua b/lualib/utils/table.lua
new file mode 100644
index 00000000..faf3ceab
--- /dev/null
+++ b/lualib/utils/table.lua
@@ -0,0 +1,373 @@
+local type = type
+local pairs = pairs
+local ipairs = ipairs
+local rawlen = rawlen
+local assert = assert
+local select = select
+
+local ceil = math.ceil
+local floor = math.floor
+local sformat = string.format
+
+local tsort = table.sort
+local tconcat = table.concat
+local tunpack = table.unpack
+
+---comment 获取`table`长度
+---@param tab table
+---@return integer
+function table.len (tab)
+ assert(type(tab) == 'table', "Invalid table.")
+ return #tab
+end
+
+---comment 获取`table`长度(跳过元方法)
+---@param tab table
+---@return integer
+function table.rawlen (tab)
+ assert(type(tab) == 'table', "Invalid table.")
+ if rawlen then
+ return rawlen(tab)
+ end
+ return #tab
+end
+
+---comment 返回数组内最大`value`
+---@param tab table
+---@return number
+function table.max(tab)
+ assert(type(tab) == 'table', "Invalid table.")
+ local s, e = 2, #tab
+ local max_value = nil
+ if e < 1 then
+ return max_value
+ end
+ max_value = tab[1]
+ while s < e do
+ if max_value < tab[s] then
+ max_value = tab[s]
+ end
+ s = s + 1
+ if max_value < tab[e] then
+ max_value = tab[e]
+ end
+ e = e - 1
+ end
+ return max_value
+end
+
+---comment 返回数组内最小`value`
+---@param tab table
+---@return number
+function table.min(tab)
+ assert(type(tab) == 'table', "Invalid table.")
+ local s, e = 2, #tab
+ local min_value = nil
+ if e < 1 then
+ return min_value
+ end
+ min_value = tab[1]
+ while s < e do
+ if min_value > tab[s] then
+ min_value = tab[s]
+ end
+ s = s + 1
+ if min_value > tab[e] then
+ min_value = tab[e]
+ end
+ e = e - 1
+ end
+ return min_value
+end
+
+---comment 获取`table`所有`key`
+---@param tab table
+---@return table @返回`keys`数组
+function table.keys (tab)
+ assert(type(tab) == 'table', "Invalid table.")
+ local list = {}
+ for k, _ in pairs(tab) do
+ list[#list+1] = k
+ end
+ return list
+end
+
+---comment 获取`table`所有`value`
+---@param tab table
+---@return table @返回`value`数组
+function table.values (tab)
+ assert(type(tab) == 'table', "Invalid table.")
+ local list = {}
+ for _, v in pairs(tab) do
+ list[#list+1] = v
+ end
+ return list
+end
+
+---comment 向左旋转数组
+---@param tab table
+---@return table @返回旋转完成后的数组
+function table.lrotate(tab)
+ assert(type(tab) == 'table', "Invalid table.")
+ local last = tab[1]
+ for i = 2, #tab do
+ tab[i-1] = tab[i]
+ end
+ tab[#tab] = last
+ return tab
+end
+
+---comment 向右旋转数组
+---@param tab table
+---@return table @返回旋转完成后的数组
+function table.rrotate(tab)
+ assert(type(tab) == 'table', "Invalid table.")
+ local last = tab[#tab]
+ for i = #tab - 1, 1, -1 do
+ tab[i+1] = tab[i]
+ end
+ tab[1] = last
+ return tab
+end
+
+---comment 反转数组
+---@param tab table @待反转的数组
+---@return table @返回`tab`
+function table.reverse (tab)
+ assert(type(tab) == 'table', "Invalid table.")
+ local s, e = 1, #tab
+ while s < e do
+ tab[s], tab[e] = tab[e], tab[s]
+ s = s + 1
+ e = e - 1
+ end
+ return tab
+end
+
+local sorts = {
+ [1] = function (list1, list2)
+ return list1[1] < list2[1]
+ end,
+ [2] = function (list1, list2)
+ return list2[1] < list1[1]
+ end,
+ [3] = function (list1, list2)
+ return list1[2] < list2[2]
+ end,
+ [4] = function (list1, list2)
+ return list2[2] < list1[2]
+ end,
+}
+
+---comment 格式化输出表内容
+---@param tab table @原始表
+---@param fmt string | nil @可自定义`key`、`value`格式内容
+---@param sep string | nil @如果表内字段多, 多个`format`字符串连接时候可能会需要用到.
+---@param sort integer | nil @默认为(1.key升序),可选:(2.key降序)、(3.value升序)、(4.value降序)
+---@return string @最终的输出内容
+function table.format (tab, fmt, sep, sort)
+ assert(type(tab) == 'table', "Invalid table.")
+ local list = {}
+ for k, v in pairs(tab) do
+ list[#list+1] = {k, v}
+ end
+ -- 根据key进行升序排列
+ tsort(list, sorts[tonumber(sort) or 1] or sorts[1])
+ -- 开始合并数据
+ for idx, item in ipairs(list) do
+ list[idx] = sformat(fmt or "%s=%s", item[1], item[2])
+ end
+ return tconcat(list, sep)
+end
+
+---comment 合并2个表
+---@param table1 table | nil @`table1`和`table2`只能有一个为空
+---@param table2 table | nil @`table1`和`table2`只能有一个为空
+---@param new_tab table | nil @可以外部传入或者内部创建
+---@return table
+local function table_merge(table1, table2, new_tab)
+ local tab = new_tab or {}
+ if type(table1) == 'table' then
+ for k, v in pairs(table1) do
+ tab[k] = type(v) ~= 'table' and v or table_merge(v, {})
+ end
+ end
+ if type(table2) == 'table' then
+ for k, v in pairs(table2) do
+ tab[k] = type(v) ~= 'table' and v or table_merge(v, {})
+ end
+ end
+ return tab
+end
+
+---comment 创建新表来合并2个字典表(不存在引用问题)
+---@param table1 table
+---@param table2 table
+---@return table @始终返回新表
+function table.new (table1, table2)
+ assert(table1 ~= table2, "You cannot merge two tables of the same type.")
+ return table_merge(table1, table2, {})
+end
+
+---comment 合并表`table2`内容到表`table1`内(不存在引用问题)
+---@param table1 table
+---@param table2 table
+---@return table @返回`table1`
+function table.lmerge (table1, table2)
+ assert(table1 ~= table2, "You cannot merge two tables of the same type.")
+ return table_merge(nil, table2, table1)
+end
+
+---comment 合并表`table1`内容到表`table2`内(不存在引用问题)
+---@param table1 table
+---@param table2 table
+---@return table @返回`table2`
+function table.rmerge (table1, table2)
+ assert(table1 ~= table2, "You cannot merge two tables of the same type.")
+ return table_merge(nil, table1, table2)
+end
+
+---comment 数组转哈希表
+---@param tab table @待转换的数组
+---@return table @返回转换后的哈希表
+function table.tohash(tab)
+ assert(type(tab) == 'table', "Invalid table.")
+ local len = #tab
+ assert(ceil(len / 2) == floor(len / 2), "Invalid key value amount.")
+ local hashtab = {}
+ for idx = 1, len, 2 do
+ hashtab[tab[idx]] = tab[idx+1]
+ end
+ return hashtab
+end
+
+---comment 哈希表转数组(列表)
+---@param tab table @待转换的哈希表
+---@return table @返回转换后的数组(列表)
+function table.tolist(tab)
+ assert(type(tab) == 'table', "Invalid table.")
+ local idx = 1
+ local list = {}
+ for k, v in pairs(tab) do
+ list[idx] = k
+ idx = idx + 1
+ list[idx] = v
+ idx = idx + 1
+ end
+ return list
+end
+
+---comment 将一个或者多可`key`->`value`构建为哈希表或者数组
+---@return table @返回构建好的table
+function table.wrap(...)
+ local len = select("#", ...)
+ assert(ceil(len / 2) == floor(len / 2), "Invalid key value amount.")
+ local list = {...}
+ local tab = {}
+ for idx = 1, len, 2 do
+ tab[list[idx]] = list[idx+1]
+ end
+ return tab
+end
+
+---comment 求一个序列或者多个序列进行函数映射之后的值
+---@param func function @回调函数, 返回值不能为`nil`
+---@param list1 table @多个数组(至少一个)
+---@return table @返回新数组
+function table.map(func, list1, ...)
+ assert(type(func) == 'function', "Invalid `function`.")
+ assert(type(list1) == 'table', "Invalid `table`.")
+ local newlist = {}
+ local lists = {list1, ...}
+ local count = #lists
+ local len
+ for i = 1, count do
+ local l = #(lists[i])
+ if not len or len <= l then
+ len = l
+ end
+ end
+ local args = {}
+ for index = 1, len do
+ for idx, list in ipairs(lists) do
+ args[idx] = list[index]
+ end
+ local o = func(tunpack(args))
+ if o ~= nil then
+ newlist[#newlist+1] = o
+ end
+ end
+ return newlist
+end
+
+---comment 过滤不符合函数条件的元素并返回新的结果数组
+---@param func function @回调函数, 返回值必须是`boolean`
+---@param list table @原始数组
+---@return table @返回新数组
+function table.filter(func, list)
+ assert(type(func) == 'function', "Invalid `function`.")
+ assert(type(list) == 'table', "Invalid `table`.")
+ local newlist = {}
+ for i = 1, #list do
+ local ok = func(list[i])
+ assert(type(ok) == 'boolean', "callback must return `true` or `false`.")
+ if ok then
+ newlist[#newlist+1] = list[i]
+ end
+ end
+ return newlist
+end
+
+---comment 过滤不符合函数条件的元素并返回新的结果数组
+---@param func function @回调函数, 返回值必须是`boolean`
+---@param list table @原始数组
+---@return number @返回计算结果
+function table.reduce(func, list)
+ assert(type(func) == 'function', "Invalid `function`.")
+ assert(type(list) == 'table', "Invalid `table`.")
+ local len = #list
+ if len <= 1 then
+ if len == 1 then
+ return list[1]
+ end
+ error("can't passed empty array.")
+ end
+ local args = {list[1], nil}
+ for i = 2, len do
+ args[2] = list[i]
+ local result = func(args[1], args[2])
+ if type(result) ~= 'number' then
+ error("return invalid result.")
+ end
+ args[1] = result
+ end
+ return args[1]
+end
+
+---comment 检查`key`是否包含在多个参数集合中.
+---@param key any @指定的`key`
+---@param ... any @`1`个或`N`个参数组成的集合
+---@return boolean @包含返回`true`, 不包含返回`false`
+function table.on(key, ...)
+ if not key then
+ return false
+ end
+ local tab = {...}
+ for i = 1, #tab do
+ if key == tab[i] then
+ return true
+ end
+ end
+ return false
+end
+
+---comment 将就表的内容转移到新表内, 并且可以直接替换为新表引用(可用于释放大表内存)
+---@param tab table @之前的表
+---@return table @新建的表
+function table.replace(tab)
+ local t = {}
+ for key, value in pairs(tab) do
+ t[key] = value
+ end
+ return t
+end
diff --git a/lualib/webhook/dingtalk.lua b/lualib/webhook/dingtalk.lua
new file mode 100644
index 00000000..8b24036a
--- /dev/null
+++ b/lualib/webhook/dingtalk.lua
@@ -0,0 +1,111 @@
+local httpc = require "httpc"
+local json = require "json"
+
+local class = require "class"
+
+local type = type
+local ipairs = ipairs
+
+local function check_error (code, response)
+ if code ~= 200 then
+ return nil, "请求失败." .. (response or "")
+ end
+ local r = json.decode(response)
+ if type(r) ~= 'table' then
+ return nil, "未知的回应."
+ end
+ if r.errcode ~= 0 then
+ return nil, r.errmsg
+ end
+ return true
+end
+
+local dingtalk = { __VERSION__ = 0.1, robot = "https://oapi.dingtalk.com/robot/send?access_token=" }
+
+-- 发送text消息
+function dingtalk.send_text (opt)
+ assert(type(opt) == 'table', "dingtalk error: 无效的参数.")
+ assert(type(opt.token) == 'string' and opt.token ~= '', "dingtalk error: 无效的Token参数.")
+ assert(type(opt.content) == 'string' and opt.content ~= '', "dingtalk error: 无效的content参数.")
+ local allmobiles = {}
+ if type(opt.mobiles) == 'table' then
+ for _, phone in ipairs(opt.mobiles) do
+ allmobiles[#allmobiles+1] = tostring(phone)
+ end
+ end
+ local code, response = httpc.json(dingtalk.robot..opt.token, nil, json.encode({ msgtype = "text", text = { content = opt.content }, at = { atMobiles = allmobiles, isAtAll = opt.atall == true} }))
+ return check_error(code, response)
+end
+
+-- 发送link消息
+function dingtalk.send_link (opt)
+ assert(type(opt) == 'table', "dingtalk error: 无效的参数.")
+ assert(type(opt.token) == 'string' and opt.token ~= '', "dingtalk error: 无效的Token参数.")
+ assert(type(opt.msg_link) == 'string' and opt.msg_link ~= '', "dingtalk error: 无效的msg_link(必须为非空字符串).")
+ assert(type(opt.msg_title) == 'string' and opt.msg_title ~= '', "dingtalk error: 无效的msg_title(必须为非空字符串).")
+ assert(type(opt.msg_describe) == 'string' and opt.msg_describe ~= '', "dingtalk error: 无效的msg_describe(必须为非空字符串).")
+ local code, response = httpc.json(dingtalk.robot..opt.token, nil, json.encode({ msgtype = "link", link = { title = opt.msg_title, text = opt.msg_describe, messageUrl = opt.msg_link, picUrl = opt.msg_pic } }))
+ return check_error(code, response)
+end
+
+-- 发送markdown消息
+function dingtalk.send_markdown (opt)
+ assert(type(opt) == 'table', "dingtalk error: 无效的参数.")
+ assert(type(opt.token) == 'string' and opt.token ~= '', "dingtalk error: 无效的Token参数.")
+ assert(type(opt.msg_title) == 'string' and opt.msg_title ~= '', "dingtalk error: 无效的msg_title(必须为非空字符串).")
+ assert(type(opt.msg_content) == 'string' and opt.msg_content ~= '', "dingtalk error: 无效的msg_content(必须为非空字符串).")
+
+ local allmobiles = {}
+ if type(opt.mobiles) == 'table' then
+ for _, phone in ipairs(opt.mobiles) do
+ allmobiles[#allmobiles+1] = tostring(phone)
+ end
+ end
+ local code, response = httpc.json(dingtalk.robot..opt.token, nil, json.encode({
+ msgtype = "markdown", markdown = { title = opt.msg_title, text = opt.msg_content }, at = { atMobiles = opt.mobiles, isAtAll = opt.atall == true }
+ }))
+ return check_error(code, response)
+end
+
+-- 发送actionCard消息
+function dingtalk.send_actioncard (opt)
+ assert(type(opt) == 'table', "dingtalk error: 无效的参数.")
+ assert(type(opt.token) == 'string' and opt.token ~= '', "dingtalk error: 无效的Token参数.")
+ assert(type(opt.msg_title) == 'string' and opt.msg_title ~= '', "dingtalk error: 无效的msg_title(必须为非空字符串).")
+ assert(type(opt.msg_describe) == 'string' and opt.msg_describe ~= '', "dingtalk error: 无效的msg_describe(必须为非空字符串).")
+
+ local btns, single_title, single_url = nil, nil, nil
+ if type(opt.single) == 'table' and (type(opt.single.title) == 'string' and opt.single.title ~= '') and (type(opt.single.url) == 'string' and opt.single.url ~= '') then
+ single_title, single_url = opt.single.title, opt.single.url
+ end
+
+ if not single_title and not single_url and type(opt.btns) == 'table' and #opt.btns >= 1 then
+ btns = {}
+ for index, btn in ipairs(opt.btns) do
+ assert(type(btn.title) == 'string' and btn.title ~= '', 'dingtalk error: btns第'..index..'个参数title无效.')
+ assert(type(btn.url) == 'string' and btn.url ~= '', 'dingtalk error: btns第'..index..'个参数url无效.')
+ btns[#btns+1] = {title = btn.title, actionURL = btn.url}
+ end
+ end
+
+ local code, response = httpc.json(dingtalk.robot..opt.token, nil, json.encode({ msgtype = "actionCard", actionCard = { title = opt.msg_title, text = opt.msg_describe, singleURL = single_url, singleTitle = single_title, btns = btns, hideAvatar = opt.hide_avatar and '1' or '0', btnOrientation = opt.btn_orientation and '1' or '0' }}))
+ return check_error(code, response)
+end
+
+-- 发送FeedCard消息
+function dingtalk.send_feedcard (opt)
+ assert(type(opt) == 'table', "dingtalk error: 无效的参数.")
+ assert(type(opt.token) == 'string' and opt.token ~= '', "dingtalk error: 无效的Token参数.")
+ assert(type(opt.msg_links) == 'table' and #opt.msg_links >= 1, "dingtalk error: 无效的msg_links参数(内部至少有一条消息).")
+ local links = {}
+ for index, link in ipairs(opt.msg_links) do
+ assert(type(link) == 'table', "dingtalk error: 无效的消息"..index)
+ assert(type(link.msg_title) == 'string' and link.msg_title ~= '', "dingtalk error: 第"..index..'个消息的msg_title无效.')
+ assert(type(link.msg_link) == 'string' and link.msg_link ~= '', "dingtalk error: 第"..index..'个消息的msg_link无效.')
+ links[#links+1] = { title = link.msg_title, messageURL = link.msg_link, picURL = link.msg_pic }
+ end
+ local code, response = httpc.json(dingtalk.robot..opt.token, nil, json.encode({ msgtype = "feedCard", feedCard = { links = links } }))
+ return check_error(code, response)
+end
+
+return dingtalk
diff --git a/lualib/XmlParser.lua b/lualib/xml2lua/XmlParser.lua
similarity index 85%
rename from lualib/XmlParser.lua
rename to lualib/xml2lua/XmlParser.lua
index 04c55c65..b5dc250f 100644
--- a/lualib/XmlParser.lua
+++ b/lualib/xml2lua/XmlParser.lua
@@ -1,15 +1,4 @@
---- this code fork from github.com/manoelcampos/xml2lua
-
-local tonumber = tonumber
-local getmetatable = getmetatable
-local setmetatable = setmetatable
-local error = error
-local pairs = pairs
-local ipairs = ipairs
-local table = table
-local string = string
-
---- @module Class providing the actual XML parser.
+-- @module Class providing the actual XML parser.
-- Available options are:
-- * stripWS
-- Strip non-significant whitespace (leading/trailing)
@@ -30,7 +19,7 @@ local string = string
---Converts the decimal code of a character to its corresponding char
--if it's a graphical char, otherwise, returns the HTML ISO code
--for that decimal value in the format code
---@param code the decimal value to convert to its respective character
+--@param code number @the decimal value to convert to its respective character
local function decimalToHtmlChar(code)
local n = tonumber(code)
if n >= 0 and n < 256 then
@@ -43,7 +32,7 @@ end
---Converts the hexadecimal code of a character to its corresponding char
--if it's a graphical char, otherwise, returns the HTML ISO code
--for that hexadecimal value in the format ode
---@param code the hexadecimal value to convert to its respective character
+--@param code number @the hexadecimal value to convert to its respective character
local function hexadecimalToHtmlChar(code)
local n = tonumber(code, 16)
if n >= 0 and n < 256 then
@@ -67,7 +56,8 @@ local XmlParser = {
_WS = '^%s*$',
_DTD1 = '',
_DTD2 = '',
- _DTD3 = '',
+ --_DTD3 = '',
+ _DTD3 = '',
_DTD4 = '',
_DTD5 = '',
@@ -104,11 +94,11 @@ local XmlParser = {
}
--- Instantiates a XmlParser object.
---@param _handler Handler module to be used to convert the XML string
+--@param _handler table @Handler module to be used to convert the XML string
-- to another formats. See the available handlers at the handler directory.
-- Usually you get an instance to a handler module using, for instance:
-- local handler = require("xmlhandler/tree").
---@param _options Options for this XmlParser instance.
+--@param _options table @Options for this XmlParser instance.
--@see XmlParser.options
function XmlParser.new(_handler, _options)
local obj = {
@@ -123,18 +113,18 @@ function XmlParser.new(_handler, _options)
end
---Checks if a function/field exists in a table or in its metatable
---@param table the table to check if it has a given function
---@param elementName the name of the function/field to check if exists
---@return true if the function/field exists, false otherwise
+--@param table table @the table to check if it has a given function
+--@param elementName string @the name of the function/field to check if exists
+--@return boolean @true if the function/field exists, false otherwise
local function fexists(table, elementName)
if table == nil then
return false
end
- if table[elementName] ~= nil then
- return true
- else
+ if table[elementName] == nil then
return fexists(getmetatable(table), elementName)
+ else
+ return true
end
end
@@ -164,10 +154,10 @@ local function parseEntities(self, s)
end
--- Parses a string representing a tag.
---@param s String containing tag text
---@return a {name, attrs} table
--- where name is the name of the tag and attrs
--- is a table containing the atributtes of the tag
+--- where name is the name of the tag and attrs
+--- is a table containing the atributtes of the tag
+--@param s string @String containing tag text
+--@return table @{name, attrs} table
local function parseTag(self, s)
local tag = {
name = string.gsub(s, self._TAG, '%1'),
@@ -178,7 +168,7 @@ local function parseTag(self, s)
tag.attrs[k] = parseEntities(self, v)
tag.attrs._ = 1
end
-
+
string.gsub(s, self._ATTR1, parseFunction)
string.gsub(s, self._ATTR2, parseFunction)
@@ -211,7 +201,7 @@ local function parseXmlDeclaration(self, xml, f)
end
if fexists(self.handler, 'decl') then
- self.handler:decl(tag, f.match, f.endMatch)
+ self.handler:decl(tag, f.match, f.endMatch)
end
return tag
@@ -256,44 +246,27 @@ end
local function _parseDtd(self, xml, pos)
-- match,endMatch,root,type,name,uri,internal
- local m,e,r,t,n,u,i
-
- m,e,r,t,u,i = string.find(xml, self._DTD1,pos)
- if m then
- return m, e, {_root=r,_type=t,_uri=u,_internal=i}
- end
+ local dtdPatterns = {self._DTD1, self._DTD2, self._DTD3, self._DTD4, self._DTD5}
- m,e,r,t,n,u,i = string.find(xml, self._DTD2,pos)
- if m then
- return m, e, {_root=r,_type=t,_name=n,_uri=u,_internal=i}
- end
-
- m,e,r,i = string.find(xml, self._DTD3,pos)
- if m then
- return m, e, {_root=r,_internal=i}
- end
-
- m,e,r,t,u = string.find(s,self._DTD4,pos)
- if m then
- return m,e,{_root=r,_type=t,_uri=u}
- end
-
- m,e,r,t,n,u = string.find(s,self._DTD5,pos)
- if m then
- return m,e,{_root=r,_type=t,_name=n,_uri=u}
+ for i, dtd in pairs(dtdPatterns) do
+ local m,e,r,t,n,u,i = string.find(xml, dtd, pos)
+ if m then
+ return m, e, {_root=r, _type=t, _name=n, _uri=u, _internal=i}
+ end
end
return nil
end
local function parseDtd(self, xml, f)
- f.match, f.endMatch, attrs = self:_parseDtd(xml, f.pos)
+ f.match, f.endMatch = _parseDtd(self, xml, f.pos)
if not f.match then
err(self, self._errstr.dtdErr, f.pos)
end
if fexists(self.handler, 'dtd') then
- self.handler:dtd(attrs._root, attrs, f.match, f.endMatch)
+ local tag = {name="DOCTYPE", value=string.sub(xml, f.match+10, f.endMatch-1)}
+ self.handler:dtd(tag, f.match, f.endMatch)
end
end
@@ -388,7 +361,7 @@ local function parseTagType(self, xml, f)
end
--- Get next tag (first pass - fix exceptions below).
---@return true if the next tag could be got, false otherwise
+--@return boolean @true if the next tag could be got, false otherwise
local function getNextTag(self, xml, f)
f.match, f.endMatch, f.text, f.endt1, f.tagstr, f.endt2 = string.find(xml, self._XML, f.pos)
if not f.match then
@@ -413,8 +386,8 @@ local function getNextTag(self, xml, f)
end
--Main function which starts the XML parsing process
---@param xml the XML string to parse
---@param parseAttributes indicates if tag attributes should be parsed or not.
+--@param xml string @the XML string to parse
+--@param parseAttributes boolean @indicates if tag attributes should be parsed or not.
-- If omitted, the default value is true.
function XmlParser:parse(xml, parseAttributes)
if type(self) ~= "table" or getmetatable(self) ~= XmlParser then
@@ -461,4 +434,4 @@ function XmlParser:parse(xml, parseAttributes)
end
XmlParser.__index = XmlParser
-return XmlParser
\ No newline at end of file
+return XmlParser
diff --git a/lualib/xml2lua/init.lua b/lualib/xml2lua/init.lua
new file mode 100644
index 00000000..397e1f26
--- /dev/null
+++ b/lualib/xml2lua/init.lua
@@ -0,0 +1,38 @@
+-- 对xml2lua的简单封装, 简化xml2lua调用流程
+local xml2lua = require "xml2lua.xml2lua"
+local xml2lua_handler = require "xml2lua.xmlhandler.tree"
+
+local type = type
+local pcall = pcall
+
+local xml = {}
+
+-- xml字符串解析
+function xml.parser(xml_data)
+ local cls = xml2lua.parser(xml2lua_handler:new())
+ -- cls:parse(xml_data)
+ local ok, err = pcall(cls.parse, cls, xml_data)
+ if not ok then
+ return err
+ end
+ return cls.handler.root
+end
+
+-- xml文件读取
+function xml.load(xml_path)
+ if type(xml_path) ~= 'string' or xml_path == '' then
+ return nil, '无效的xml文件路径.'
+ end
+ local xfile, error = xml2lua.loadFile(xml_path)
+ if xfile then
+ return xml.parser(xfile)
+ end
+ return xfile, error
+end
+
+-- 将table序列化为xml
+function xml.toxml( ... )
+ return xml2lua.toXml(...)
+end
+
+return xml
diff --git a/lualib/xml2lua.lua b/lualib/xml2lua/xml2lua.lua
old mode 100644
new mode 100755
similarity index 54%
rename from lualib/xml2lua.lua
rename to lualib/xml2lua/xml2lua.lua
index 0725fbed..d7222be3
--- a/lualib/xml2lua.lua
+++ b/lualib/xml2lua/xml2lua.lua
@@ -1,6 +1,4 @@
---- this code fork from github.com/manoelcampos/xml2lua
-
---- @module Module providing a non-validating XML stream parser in Lua.
+-- @module Module providing a non-validating XML stream parser in Lua.
--
-- Features:
-- =========
@@ -51,21 +49,12 @@
--
--@author Paul Chakravarti (paulc@passtheaardvark.com)
--@author Manoel Campos da Silva Filho
-local class = require "class"
-local XmlParser = require("XmlParser")
-
-local tonumber = tonumber
-local getmetatable = getmetatable
-local setmetatable = setmetatable
-local error = error
-local pairs = pairs
-local ipairs = ipairs
-local table = table
-local string = string
+local xml2lua = {}
+local XmlParser = require("xml2lua.XmlParser")
---Recursivelly prints a table in an easy-to-ready format
---@param tb The table to be printed
---@param level the indentation level to start with
+--@param tb table @The table to be printed
+--@param level integer @the indentation level to start with
local function printableInternal(tb, level)
if tb == nil then
return
@@ -84,16 +73,13 @@ local function printableInternal(tb, level)
end
---Instantiates a XmlParser object to parse a XML string
---@param handler Handler module to be used to convert the XML string
+--@param handler table Handler module to be used to convert the XML string
--to another formats. See the available handlers at the handler directory.
-- Usually you get an instance to a handler module using, for instance:
-- local handler = require("xmlhandler/tree").
---@return a XmlParser object used to parse the XML
+--@return string @XmlParser object used to parse the XML
--@see XmlParser
-
-local xml2lua = {}
-
-function xml2lua.parser(handler)
+function xml2lua.parser(handler)
if handler == xml2lua then
error("You must call xml2lua.parse(handler) instead of xml2lua:parse(handler)")
end
@@ -111,15 +97,15 @@ function xml2lua.parser(handler)
end
---Recursivelly prints a table in an easy-to-ready format
---@param tb The table to be printed
+--@param tb table The table to be printed
function xml2lua.printable(tb)
printableInternal(tb)
end
---Handler to generate a string prepresentation of a table
--Convenience function for printHandler (Does not support recursive tables).
---@param t Table to be parsed
---@return a string representation of the table
+--@param t table to be parsed
+--@return string @representation of the table
function xml2lua.toString(t)
local sep = ''
local res = ''
@@ -140,16 +126,86 @@ function xml2lua.toString(t)
end
--- Loads an XML file from a specified path
--- @param xmlFilePath the path for the XML file to load
--- @return the XML loaded file content
+-- @param xmlFilePath string @the path for the XML file to load
+-- @return string @the XML loaded file content
function xml2lua.loadFile(xmlFilePath)
local f, e = io.open(xmlFilePath, "r")
if f then
--Gets the entire file content and stores into a string
- return f:read("*a")
+ local content = f:read("*a")
+ f:close()
+ return content
end
error(e)
end
+---comment Gets an _attr element from a table that represents the attributes of an XML tag,
+--and generates a XML String representing the attibutes to be inserted
+--into the openning tag of the XML
+--@param attrTable table @from where the _attr field will be got
+--@return string @a XML String representation of the tag attributes
+local function attrToXml(attrTable)
+ local s = ""
+ for k, v in pairs(attrTable or {}) do
+ s = s .. " " .. k .. "=" .. '"' .. v .. '"'
+ end
+ return s
+end
+
+---Gets the first key of a given table
+local function getFirstKey(tb)
+ if type(tb) == "table" then
+ for k, _ in pairs(tb) do
+ return k
+ end
+ return nil
+ end
+
+ return tb
+end
+
+---Converts a Lua table to a XML String representation.
+--@param tb table @Table to be converted to XML
+--@param tableName string @Name of the table variable given to this function, to be used as the root tag.
+--@param level integer @Only used internally, when the function is called recursively to print indentation
+--@return string @String representing the table content in XML
+function xml2lua.toXml(tb, tableName, level)
+ local level = level or 1
+ local firstLevel = level
+ local spaces = string.rep(' ', level*2)
+ local xmltb = level == 1 and {'<'..tableName..'>'} or {}
+
+ for k, v in pairs(tb) do
+ if type(v) == "table" then
+ --If the keys of the table are a number, it represents an array
+ if type(k) == "number" then
+ local attrs = attrToXml(v._attr)
+ v._attr = nil
+ table.insert(xmltb,
+ spaces..'<'..tableName..attrs..'>\n'..xml2lua.toXml(v, tableName, level+1)..
+ '\n'..spaces..''..tableName..'>')
+ else
+ level = level + 1
+ if type(getFirstKey(v)) == "number" then
+ table.insert(xmltb, spaces..xml2lua.toXml(v, k, level))
+ else
+ local attrs = attrToXml(v._attr)
+ v._attr = nil
+ table.insert(xmltb,
+ spaces..'<'..k..attrs..'>\n'.. xml2lua.toXml(v, k, level+1)..
+ '\n'..spaces..''..k..'>')
+ end
+ end
+ else
+ table.insert(xmltb, spaces..'<'..k..'>'..tostring(v)..''..k..'>')
+ end
+ end
+
+ if firstLevel == 1 then
+ table.insert(xmltb, ''..tableName..'>\n')
+ end
+ return table.concat(xmltb, "\n")
+end
+
return xml2lua
\ No newline at end of file
diff --git a/lualib/xmlhandler/README.md b/lualib/xml2lua/xmlhandler/README.md
old mode 100644
new mode 100755
similarity index 99%
rename from lualib/xmlhandler/README.md
rename to lualib/xml2lua/xmlhandler/README.md
index a1a5e326..e70323f2
--- a/lualib/xmlhandler/README.md
+++ b/lualib/xml2lua/xmlhandler/README.md
@@ -12,4 +12,4 @@ Then, you have to use one the handler instance when getting an instance of the X
Notice the module `xml2lua` should have been loaded before using `require("xml2lua")`.
This way, the handler is called internally when the `parser:parse(xml)` function is called.
-Check the documentation on the root directory for complete examples.
+Check the documentation on the root directory for complete examples.
\ No newline at end of file
diff --git a/lualib/xmlhandler/dom.lua b/lualib/xml2lua/xmlhandler/dom.lua
old mode 100644
new mode 100755
similarity index 77%
rename from lualib/xmlhandler/dom.lua
rename to lualib/xml2lua/xmlhandler/dom.lua
index 7ed27fc7..236bbb45
--- a/lualib/xmlhandler/dom.lua
+++ b/lualib/xml2lua/xmlhandler/dom.lua
@@ -1,4 +1,12 @@
----- @module Handler to generate a DOM-like node tree structure with
+local function init()
+ return {
+ options = {commentNode=1, piNode=1, dtdNode=1, declNode=1},
+ current = { _children = {n=0}, _type = "ROOT" },
+ _stack = {}
+ }
+end
+
+-- @module Handler to generate a DOM-like node tree structure with
-- a single ROOT node parent - each node is a table comprising
-- the fields below.
--
@@ -26,14 +34,24 @@
--
--@author Paul Chakravarti (paulc@passtheaardvark.com)
--@author Manoel Campos da Silva Filho
-local dom = {
- options = {commentNode=1, piNode=1, dtdNode=1, declNode=1},
- current = { _children = {n=0}, _type = "ROOT" },
- _stack = {}
-}
+local dom = init()
+
+---Instantiates a new handler object.
+--Each instance can handle a single XML.
+--By using such a constructor, you can parse
+--multiple XML files in the same application.
+--@return table @the handler instance
+function dom:new()
+ local obj = init()
+
+ obj.__index = self
+ setmetatable(obj, self)
+
+ return obj
+end
---Parses a start tag.
--- @param tag a {name, attrs} table
+-- @param tag table @a {name, attrs} table
-- where name is the name of the tag and attrs
-- is a table containing the atributtes of the tag
function dom:starttag(tag)
@@ -54,7 +72,8 @@ function dom:starttag(tag)
end
---Parses an end tag.
--- @param tag a {name, attrs} table
+-- @param tag table @a {name, attrs} table
+-- @param s string @a {name, attrs} table
-- where name is the name of the tag and attrs
-- is a table containing the atributtes of the tag
function dom:endtag(tag, s)
@@ -66,10 +85,11 @@ function dom:endtag(tag, s)
end
table.remove(self._stack)
+ self.current = self._stack[#self._stack]
end
---Parses a tag content.
--- @param text text to process
+-- @param text string @text to process
function dom:text(text)
local node = { _type = "TEXT",
_text = text
@@ -78,7 +98,7 @@ function dom:text(text)
end
---Parses a comment tag.
--- @param text comment text
+-- @param text string @comment text
function dom:comment(text)
if self.options.commentNode then
local node = { _type = "COMMENT",
@@ -89,7 +109,7 @@ function dom:comment(text)
end
--- Parses a XML processing instruction (PI) tag
--- @param tag a {name, attrs} table
+-- @param tag table @a {name, attrs} table
-- where name is the name of the tag and attrs
-- is a table containing the atributtes of the tag
function dom:pi(tag)
@@ -103,7 +123,7 @@ function dom:pi(tag)
end
---Parse the XML declaration line (the line that indicates the XML version).
--- @param tag a {name, attrs} table
+-- @param tag table @a {name, attrs} table
-- where name is the name of the tag and attrs
-- is a table containing the atributtes of the tag
function dom:decl(tag)
@@ -117,7 +137,7 @@ function dom:decl(tag)
end
---Parses a DTD tag.
--- @param tag a {name, attrs} table
+-- @param tag table @a {name, attrs} table
-- where name is the name of the tag and attrs
-- is a table containing the atributtes of the tag
function dom:dtd(tag)
@@ -132,4 +152,5 @@ end
---Parses CDATA tag content.
dom.cdata = dom.text
-return dom
\ No newline at end of file
+dom.__index = dom
+return dom
diff --git a/lualib/xml2lua/xmlhandler/print.lua b/lualib/xml2lua/xmlhandler/print.lua
new file mode 100755
index 00000000..83c4a146
--- /dev/null
+++ b/lualib/xml2lua/xmlhandler/print.lua
@@ -0,0 +1,92 @@
+-- @module Handler to generate a simple event trace which
+-- outputs messages to the terminal during the XML
+-- parsing, usually for debugging purposes.
+--
+-- License:
+-- ========
+--
+-- This code is freely distributable under the terms of the [MIT license](LICENSE).
+--
+--@author Paul Chakravarti (paulc@passtheaardvark.com)
+--@author Manoel Campos da Silva Filho
+local print = {}
+
+---Parses a start tag.
+-- @param tag table @a {name, attrs} table
+-- where name is the name of the tag and attrs
+-- is a table containing the atributtes of the tag
+function print:starttag(tag)
+ io.write("Start : "..tag.name.."\n")
+ if tag.attrs then
+ for k,v in pairs(tag.attrs) do
+ io.write(string.format(" + %s='%s'\n", k, v))
+ end
+ end
+end
+
+---Parses an end tag.
+-- @param tag table @a {name, attrs} table
+-- where name is the name of the tag and attrs
+-- is a table containing the atributtes of the tag
+function print:endtag(tag)
+ io.write("End : "..tag.name.."\n")
+end
+
+---Parses a tag content.
+-- @param text string @text to process
+function print:text(text)
+ io.write("Text : "..text.."\n")
+end
+
+---Parses CDATA tag content.
+-- @param text string @CDATA content to be processed
+function print:cdata(text)
+ io.write("CDATA : "..text.."\n")
+end
+
+---Parses a comment tag.
+-- @param text string @comment text
+function print:comment(text)
+ io.write("Comment : "..text.."\n")
+end
+
+---Parses a DTD tag.
+-- @param tag table @a {name, attrs} table
+-- where name is the name of the tag and attrs
+-- is a table containing the atributtes of the tag
+function print:dtd(tag)
+ io.write("DTD : "..tag.name.."\n")
+ if tag.attrs then
+ for k,v in pairs(tag.attrs) do
+ io.write(string.format(" + %s='%s'\n", k, v))
+ end
+ end
+end
+
+--- Parse a XML processing instructions (PI) tag.
+-- @param tag table @a {name, attrs} table
+-- where name is the name of the tag and attrs
+-- is a table containing the atributtes of the tag
+function print:pi(tag)
+ io.write("PI : "..tag.name.."\n")
+ if tag.attrs then
+ for k,v in pairs(tag.attrs) do
+ io. write(string.format(" + %s='%s'\n",k,v))
+ end
+ end
+end
+
+---Parse the XML declaration line (the line that indicates the XML version).
+-- @param tag table @a {name, attrs} table
+-- where name is the name of the tag and attrs
+-- is a table containing the atributtes of the tag
+function print:decl(tag)
+ io.write("XML Decl : "..tag.name.."\n")
+ if tag.attrs then
+ for k,v in pairs(tag.attrs) do
+ io.write(string.format(" + %s='%s'\n", k, v))
+ end
+ end
+end
+
+return print
\ No newline at end of file
diff --git a/lualib/xmlhandler/tree.lua b/lualib/xml2lua/xmlhandler/tree.lua
old mode 100644
new mode 100755
similarity index 87%
rename from lualib/xmlhandler/tree.lua
rename to lualib/xml2lua/xmlhandler/tree.lua
index 7703799d..69320976
--- a/lualib/xmlhandler/tree.lua
+++ b/lualib/xml2lua/xmlhandler/tree.lua
@@ -3,12 +3,11 @@ local function init()
root = {},
options = {noreduce = {}}
}
-
- obj._stack = {obj.root, n=1}
+ obj._stack = {obj.root, n = 1}
return obj
end
---- @module XML Tree Handler.
+-- @module XML Tree Handler.
-- Generates a lua table from an XML content string.
-- It is a simplified handler which attempts
-- to generate a more 'natural' table based structure which
@@ -61,7 +60,7 @@ local tree = init()
--Each instance can handle a single XML.
--By using such a constructor, you can parse
--multiple XML files in the same application.
---@return the handler instance
+--@return table @the handler instance
function tree:new()
local obj = init()
@@ -73,11 +72,10 @@ end
--Gets the first key of a table
--@param tb table to get its first key
---@return the table's first key, nil if the table is empty
---or the given parameter if it isn't a table
+--@return table @the table's first key, nil if the table is empty or the given parameter if it isn't a table
local function getFirstKey(tb)
if type(tb) == "table" then
- for k, v in pairs(tb) do
+ for k, _ in pairs(tb) do
return k
end
@@ -87,8 +85,10 @@ local function getFirstKey(tb)
return tb
end
---- Recursively removes redundant vectors for nodes
--- with single child elements
+--- Recursively removes redundant vectors for nodes with single child elements
+---@param node table
+---@param key string
+---@param parent table
function tree:reduce(node, key, parent)
for k,v in pairs(node) do
if type(v) == 'table' then
@@ -104,7 +104,7 @@ function tree:reduce(node, key, parent)
end
---Parses a start tag.
--- @param tag a {name, attrs} table
+-- @param tag table @a {name, attrs} table
-- where name is the name of the tag and attrs
-- is a table containing the atributtes of the tag
function tree:starttag(tag)
@@ -115,7 +115,7 @@ function tree:starttag(tag)
--Table in the stack representing the tag being processed
local current = self._stack[#self._stack]
-
+
if current[tag.name] then
table.insert(current[tag.name], node)
else
@@ -126,7 +126,7 @@ function tree:starttag(tag)
end
---Parses an end tag.
--- @param tag a {name, attrs} table
+-- @param tag table @a {name, attrs} table
-- where name is the name of the tag and attrs
-- is a table containing the atributtes of the tag
function tree:endtag(tag, s)
@@ -142,18 +142,18 @@ function tree:endtag(tag, s)
self:reduce(prev, nil, nil)
end
- local firstKey = getFirstKey(current)
+ getFirstKey(current)
table.remove(self._stack)
end
---Parses a tag content.
--- @param t text to process
-function tree:text(t)
+-- @param text string text to process
+function tree:text(text)
local current = self._stack[#self._stack]
- table.insert(current, t)
+ table.insert(current, text)
end
---Parses CDATA tag content.
tree.cdata = tree.text
tree.__index = tree
-return tree
\ No newline at end of file
+return tree
diff --git a/lualib/xmlhandler/print.lua b/lualib/xmlhandler/print.lua
deleted file mode 100644
index e8c03041..00000000
--- a/lualib/xmlhandler/print.lua
+++ /dev/null
@@ -1,108 +0,0 @@
----@module Handler to generate a simple event trace which
---outputs messages to the terminal during the XML
---parsing, usually for debugging purposes.
---
--- License:
--- ========
---
--- This code is freely distributable under the terms of the [MIT license](LICENSE).
---
---@author Paul Chakravarti (paulc@passtheaardvark.com)
---@author Manoel Campos da Silva Filho
-local print = {}
-
----Parses a start tag.
--- @param tag a {name, attrs} table
--- where name is the name of the tag and attrs
--- is a table containing the atributtes of the tag
--- @param s position where the tag starts
--- @param e position where the tag ends
-function print:starttag(tag, s, e)
- io.write("Start : "..tag.name.."\n")
- if tag.attrs then
- for k,v in pairs(tag.attrs) do
- io.write(string.format(" + %s='%s'\n", k, v))
- end
- end
-end
-
----Parses an end tag.
--- @param tag a {name, attrs} table
--- where name is the name of the tag and attrs
--- is a table containing the atributtes of the tag
--- @param s position where the tag starts
--- @param e position where the tag ends
-function print:endtag(tag, s, e)
- io.write("End : "..tag.name.."\n")
-end
-
----Parses a tag content.
--- @param text text to process
--- @param s position where the tag starts
--- @param e position where the tag ends
-function print:text(text, s, e)
- io.write("Text : "..text.."\n")
-end
-
----Parses CDATA tag content.
--- @param text CDATA content to be processed
--- @param s position where the tag starts
--- @param e position where the tag ends
-function print:cdata(text, s, e)
- io.write("CDATA : "..text.."\n")
-end
-
----Parses a comment tag.
--- @param text comment text
--- @param s position where the tag starts
--- @param e position where the tag ends
-function print:comment(text, s, e)
- io.write("Comment : "..text.."\n")
-end
-
----Parses a DTD tag.
--- @param tag a {name, attrs} table
--- where name is the name of the tag and attrs
--- is a table containing the atributtes of the tag
--- @param s position where the tag starts
--- @param e position where the tag ends
-function print:dtd(tag, s, e)
- io.write("DTD : "..tag.name.."\n")
- if tag.attrs then
- for k,v in pairs(tag.attrs) do
- io.write(string.format(" + %s='%s'\n", k, v))
- end
- end
-end
-
---- Parse a XML processing instructions (PI) tag.
--- @param tag a {name, attrs} table
--- where name is the name of the tag and attrs
--- is a table containing the atributtes of the tag
--- @param s position where the tag starts
--- @param e position where the tag ends
-function print:pi(tag, s, e)
- io.write("PI : "..tag.name.."\n")
- if tag.attrs then
- for k,v in pairs(tag.attrs) do
- io. write(string.format(" + %s='%s'\n",k,v))
- end
- end
-end
-
----Parse the XML declaration line (the line that indicates the XML version).
--- @param tag a {name, attrs} table
--- where name is the name of the tag and attrs
--- is a table containing the atributtes of the tag
--- @param s position where the tag starts
--- @param e position where the tag ends
-function print:decl(tag, s, e)
- io.write("XML Decl : "..tag.name.."\n")
- if tag.attrs then
- for k,v in pairs(tag.attrs) do
- io.write(string.format(" + %s='%s'\n", k, v))
- end
- end
-end
-
-return print
\ No newline at end of file
diff --git a/private1024.pem b/private1024.pem
new file mode 100644
index 00000000..1939c3c3
--- /dev/null
+++ b/private1024.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDc84CClRm5VqlzhvG7Izfff/VBhycuhxYEubSQp6NXT8virjlp
+e/g0dW94d1UENsEZYiLxepVwkHKNjxlbKMiLTjk+hs+S2g6U7bIbFLClZAdD9Bf+
+wuKD5YUUX7cRLzFEIFCNLn9E7HZ6/8iRkMtMmZf6Dx0LG/WmZ3WN1CnqwwIDAQAB
+AoGBAIOC7BrNZGJMks+QopEghUEiiHhYWZn4DcMCRddT8IUnmdNyn/mJMFMJEzBA
+1vmLHaReJS5WKFy3nXdklVMNE0+7khDciu/sDTSgxtZJRWvBwdYmgkzNz8jjkOzH
+ZCQuqUu12AS5aFieLRB6i/FeGZiJGEoT+/KBMlVNi11xJXsBAkEA/uu88SwnjnbY
+nbMwiDyWhK3/QE3vr82DBg0Z9mrmCnw7iXjB5pKUJLQbAO0NiCbi/HEkwvRkQpgx
+qzh8MB4gnQJBAN3i81AnOPPUAOS2Gh1jHpNyT/zr9BZ27aKbXL7eraUky3uEdNps
+rWCboyXxR0aF0BSJM4K4rL+Alveqnv/M6t8CQEuTzJKcCqY8KgCnLY5WmDGB/Jku
+Ag/XGC9lFvttugIFzwj02lfnwTAYjaD6pvZkwQsi6Ek8d7UetisTNg52ACkCQDAA
+gybZ9WY6fR79jlTBNsIrPsa2vQ2HGQ3OkpfwUJyjgynrk+QVEsUNppP0yLinBkcL
+D4u+LBEZ3o8h6Ffqmv0CQQCKBHHaAjYAhUd3lGrseN45QSw4VpARQ7/bBKimrq+d
+JCNdEpTOt6eQs8t43uOCeDjZ0H33KXG8ofcLprYE5K4G
+-----END RSA PRIVATE KEY-----
diff --git a/private2048.pem b/private2048.pem
new file mode 100644
index 00000000..90ef8afd
--- /dev/null
+++ b/private2048.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAuhsl0nR17nqITIkMYqzrhbT5me5+Q+G387spzR8KWbVujoRF
+Qa6DNzZS8HPNmBW8HjVZJ29uBAAtpWpoERdwvT31Kag+q9eSpX1d02uTd43cESNT
+b1bYCYd3LHdPZ9GSH3gIfD/fk2P8zJytzED7cT1gDUqzCcxi3cvH6/yHb6hmYmNs
+Qo1MLTn3kxyzU32J3nhNEZTlHq1XE9ydqLVqDCMRPOUuOsXcIb9ASZh3ExSPU2cx
++8WMAWWoCw/kO7EJ4Vinl8NDbsRoyXjweekYZ0bH9YrN4y05KLMhsQhoxBdmKdcJ
+ErPwYad17vtmhTpdwEpCriL/iZDLUDOz1G2ngwIDAQABAoIBAQC11cqZm0KS1NQR
+e6JHU073oAB0f0qNRqB2GrvX9+kkB5pS0zfb2gfIzWIyH+OUIkBgf51xY3VpoUb4
+JUQy1uVHcZ71qbY6LnHREfG3nZdDK68Ga66czYxdmyc8ogJKnMAZ0SzxQXNQTlR1
+EuzY8fD7Do2nzwGppDJBJVdb4qvt0kAEBu+0O5srUuGNzJJzktAIwzk7aFpVhw+l
+cCS6ZY5rRM0AeQ2tomVvf+81bs8JOG8T7JtJ+o2cL+bZjRKhCHliQZQzByEQtOZx
+J8s0mr3Tj9FzXfy6pUYSO1+GiWgrjV0nnaKj+sqE/MuuNam/nA64us8kLE7RA3gH
+n7tLJwDBAoGBAOLoQf6KuE7D0WTVj5tb/CnAtPsZ18tClvElPgex/32Cjwuwy/Du
+yPn/EIWO2EJX1dtRdWe34OJYtOcZk4qbFiyB/rxjsr6UAPNO/tY55nW2OqZ944Dq
+3duzUb3jVeAI3HyGksvHZwBJ5t7o80BBYUdk28G2ZNjaalL1HqgXzjxjAoGBANH3
+rZPQU3yvuFQdL3HlV3h7PNyNwkyjVMgD+jCdIrySIw0DqisJFgWoVxNl92lhUkir
+ZPY8dzZ6uFh6tx12QyoO2VG6Y9Pf3GvjHC1yUQqWu///+jvHwUqdPPQgHXvwKSOU
+eJVaseNHwBNV35AEdRwMDOsqQUN30lhoa5fy8AJhAoGAXQR9WU2gtJlNk5qAnl2d
+B7i5+F3luqt3mS99OEZdyCPnZBF76S7aMLHBIh8mxDuhraC9EmGszN00e7BebWma
+M3Cu7qeoNLwTj6qIiWV+9i5X6LyesNCXVmMyVTeGkqrPSDUapHL/5HxnKmYwodyr
+dksAU27j9InFIHDfumTX5KUCgYEAoXsuCOeIvfVa+33ytlLfAe8t8KYpz80x8B52
+9Zp0U7jEskamQjDbugAs7+NU87wAj5kZrfL08HZTfuDqIgOJRjhjVOLX0eRyXpst
+WZp4z378Gbfh2MYZV2w0q8BjTKV4zj9qudslwpm1FGnP5bA37RkrelVmGiB2Kr4s
+OZGCmyECgYBhXtk5BfZocI+l2/R2Yncn6Gx8s4s7bD+Fr5LE3FkU1neOuk7Yf1Uu
+xf41e3o5YBxbt51T+pNuzF3BMxsXvp9a5HxbPsmGE99BLFwR9DompoLKeUlJcy6f
+xJyXi8HBgR0OWNoTjKgaUmTgqKK5LI49C9C4Jr4c3nelo6DBb66vyw==
+-----END RSA PRIVATE KEY-----
diff --git a/private4096.pem b/private4096.pem
new file mode 100644
index 00000000..fdecc78a
--- /dev/null
+++ b/private4096.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAqkEuMgW6/rprdpHgAdr4IoGNGSfTQpCvojGHxrt3JTj+Mir4
+lZJw17cvMVfbnpWkCZ0H2jcxLR9t/blCSsNDbxe99Xbib6q6SfIkAtL3BvUU0TTz
+D5ZjZR+Ko3kA+wq6yHPa8KW49659IXJzEW9Ci2SJmj6lqrTw0F0ZXDso5ILU1Ujr
+YDJdT23hyygh4SI8geDtfmv88iP+V2emhIFgq8BKvExcxlBjX4kzUU/qDddo0OUr
+A/wpSSPPAkXYKZ4P/0SZqbkAHNfQFzMcGbf9sGAszXI1QjwomV95Ro0IJ8HCDG2h
+BvdR4AqAXI3o+DumF7kHT2pHUUP8yrodVEwWcmCjO9bPh+bm3sketDIgjs6cwuko
+hoZFr7aYp9o9l0B6lbQXdg8isut2ebXYru+Rz5KJ2Ca5fOk4PHHMbZ4murBUsQq+
+XT8q8Sldxl7IqPcSEKzLaW+JNMyZ4nX/njARA9cTJDTWp38EqxUdzo1ZifVMZBeI
++xIyqOuyfp/zUpaoeGbCuGQHJ8A7WHagQkh9x1gjOtlIdFuX2scSHrhbbsccmIPJ
+J18MquM8SnauF2cW9oLo/8n0dMmaXJFqb5sKgjitPcy+usA6e8ZquwWuDheSuEo5
+p/S7ti02hGhWTzCWvq/nnZnEvUp642LKcIYI8oJxXAFEGPm3HaucWGPNuqECAwEA
+AQKCAgBiv8OHiANZ086YyyteaB8cBIBOYucJ6Nm1/Xx/LCSDFnd5qardNj71H27j
+882SQcEPQmFXlEOVpHErhNSKgI8QVHj9cqMLmb7LPQLeEHbvNh+I6GlCVTv5Xz4O
+axTChwqnUWtfwP9zyWUSjUtohVvXdczKBiQYyzIR6K3Y7Qde4vOHce/zr8KnW1hn
+eTONXUozGr8lYIUp/O58y8WtfU0Q0UuHw9Lbw6yfsPhu5ScgOBy3bPRyL0PTBE4B
+R3mFSgSFTsjBxGfoUtSDYEWgNTOTpXTXBLMXY8U4kEPLQ+nDBYMDqmKHo9N9Wxnw
+lO7Xa+F+SPeQfFg5LnHlM+XnCurJIjctFBC+MnrGOFRMyTonUZJO7/UwQl4GkF65
+66GEsfTe4b4/JK6wr86kH2VJsJ/AW3mwQLQKjgv65Z9WyDsc7KB0FWqjU++h8DPB
+xQnbetnFL/q5tRCW2AlH/CLrckc543/jCk4KQcJ1lEyvokfkbl8aPacztIS4BKbq
+bWNkI3TmpGIzdBPwqLCjx41G5Ct+TwBRfsK4hWvYiW86jlcrjxwDqcAjvxLMIJ5X
+DdAQaRzPZQNg5vVqS+2GQ6AhMstwraMjRT/UMx5uoKS5toWRZQxdwn+ctvKC7sFU
+FGbVNwXL+zaRH4bnrZ3oGFFrVdZKNyfRR9e7gQCmICxM5QUdAQKCAQEA1nkYb3eT
+BiPSXXFWyhZhoF5+Db7cxdt0HDtYmryYz6H/J2EzRvUcVuMFcAmm0FJBBPinvawR
+hYI1B20Q9RjQJmp2ffsXCUbkdq3w70UbNup6vC/4aokIVrt0Oh2bkuCiVV/f8y8B
+YAhyYW6q1ril4m5KumrinYlzcF212wkpx9uL44rkNRFliCUV3F8RUomBmuHZ1V1G
+PQRvpyHWZJwUeeUAWqKN5qCfaUFz/yaEX7EF4c7PXoVVp40OMBuDnZjkVod24kBr
+uoj7JabAr1EPt8dQhyfZvwpwiyYfZa8FbdeFtXNLEe5FZMaBtzrAPW4Z89Sa48zj
+lJVDELZ63W2F+QKCAQEAyzhJNDpxebbXKyRD90D+TxIc3VMWPMa8DuuVz99+ROzX
+KIuxBeH2K2lEtj31aeLahppnbis4B6u7Yh05bV88n4mfBOsxUMhl0Be5QI/cfPXv
+52UKYkZE6qzIOiOi1prJy6mUFqc2z55YESx1pDSa+SvJYI5XMr0c9UpkXbMc739M
+g5bskx9tmZHoCGaJ++RBCP1hxpq39vJByruJSwjbKWsVMvU14fpy89yPiS40AfLF
+3XQgBmy3MZ+0QVDUNdKSwBkcES8EkgALXo3eaCXSy+NqdqllUct/b3G0oUbc8TI7
+NRwtGTMdz2pecw9vXa/ig8Tq0ZlYMfKHOUg2QKvj6QKCAQEAqGbvM9789447CJoM
+3qMSRvzLF3ntGgKFugEzQlSh3C7EDSS6QZYGiYa6Z018yQg8+21PMJQiMeWaQ9l0
+vi6cif2ASs1UOjmK/FD55LYrd0RH2OoFsYklngyUZ2mGFZ8Cd+zPCMC44LHhNfXS
+eMUFo7ScQqHYjIA3v1wlhfY88yvFPIZ7R9wAEBWmg6G2FUvZE0cRZwJVO2X3UZE1
+KUyQm2GflIscxqEKang1X3vb5tM13icoFny1U9li8Y05HA7IA9VcGK0iqZYTNW4o
+z7/jipca+PTmeaX11py5fHsf1S6sU1xS7qJbpJRll/yuo82G3Tjr4cCoVauZvE68
+TI9J6QKCAQAkYqGIvmYO2tPPn6CjpnliAuY0Imo624JUUY3zOBrNkHI9ijVZzklb
+IG/zCUjlen6R1xdpvEc96FuWh5D+qiyai/Ny2AFua1L/XSAIFTnvDcG0dnzTd61j
+LyhycGr5baFv257uJ2ZC6iDugj1V9y1AK7zUkue95+pFaNprhGRL5Uj3zo/xD5F6
+C4u15VYTSZzzVRqqio0ho+JvwAAm9SD4W3niM9E/8q2eSAFTGHirWKJgsigBvnlW
+YzfM8gHs2RT5XAWQdhCla2idt1z43LzPUJqBQHcpm/vnIj6rGZr5fHrpWXAhsOtH
+dc4PX9YauiEeYqWAfaoy1y+q6+j6z0vxAoIBAA5KfaRS2+4THfqRHBsUkSNlQx60
+Ns2gQ2tjLtTCu7o45AjMUca5pkbnKU6+aLYMgYYqPw0pl8EdFJEwNiv3f0L0KWCG
+aV5YasjIqRvN1wG5PzGeraGBE4W2qAP0xKKivep1oXOThgwyhlFdKic9/v9Ww9yz
+3kmmRoNEooG52Vj6JN8pRPp+Z0pg/DP0fo408wt3webv4fhwNV4bd0DmdAmIOYhd
+uGLpi1dvcNAwNd0V+0BAD1Ne3LsWGz5sa6i6vwsbXhqkPbaJg0q2VNpq2r0CM7tf
+avK7GVXsdwl/PghGQ3ct3KYqnT8kYQJwbswWfkJfaVFo5AqOhLEsNzAvpK0=
+-----END RSA PRIVATE KEY-----
diff --git a/public1024.pem b/public1024.pem
new file mode 100644
index 00000000..3e385418
--- /dev/null
+++ b/public1024.pem
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDc84CClRm5VqlzhvG7Izfff/VB
+hycuhxYEubSQp6NXT8virjlpe/g0dW94d1UENsEZYiLxepVwkHKNjxlbKMiLTjk+
+hs+S2g6U7bIbFLClZAdD9Bf+wuKD5YUUX7cRLzFEIFCNLn9E7HZ6/8iRkMtMmZf6
+Dx0LG/WmZ3WN1CnqwwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/public2048.pem b/public2048.pem
new file mode 100644
index 00000000..892ef942
--- /dev/null
+++ b/public2048.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuhsl0nR17nqITIkMYqzr
+hbT5me5+Q+G387spzR8KWbVujoRFQa6DNzZS8HPNmBW8HjVZJ29uBAAtpWpoERdw
+vT31Kag+q9eSpX1d02uTd43cESNTb1bYCYd3LHdPZ9GSH3gIfD/fk2P8zJytzED7
+cT1gDUqzCcxi3cvH6/yHb6hmYmNsQo1MLTn3kxyzU32J3nhNEZTlHq1XE9ydqLVq
+DCMRPOUuOsXcIb9ASZh3ExSPU2cx+8WMAWWoCw/kO7EJ4Vinl8NDbsRoyXjweekY
+Z0bH9YrN4y05KLMhsQhoxBdmKdcJErPwYad17vtmhTpdwEpCriL/iZDLUDOz1G2n
+gwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/public4096.pem b/public4096.pem
new file mode 100644
index 00000000..5da13134
--- /dev/null
+++ b/public4096.pem
@@ -0,0 +1,14 @@
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqkEuMgW6/rprdpHgAdr4
+IoGNGSfTQpCvojGHxrt3JTj+Mir4lZJw17cvMVfbnpWkCZ0H2jcxLR9t/blCSsND
+bxe99Xbib6q6SfIkAtL3BvUU0TTzD5ZjZR+Ko3kA+wq6yHPa8KW49659IXJzEW9C
+i2SJmj6lqrTw0F0ZXDso5ILU1UjrYDJdT23hyygh4SI8geDtfmv88iP+V2emhIFg
+q8BKvExcxlBjX4kzUU/qDddo0OUrA/wpSSPPAkXYKZ4P/0SZqbkAHNfQFzMcGbf9
+sGAszXI1QjwomV95Ro0IJ8HCDG2hBvdR4AqAXI3o+DumF7kHT2pHUUP8yrodVEwW
+cmCjO9bPh+bm3sketDIgjs6cwukohoZFr7aYp9o9l0B6lbQXdg8isut2ebXYru+R
+z5KJ2Ca5fOk4PHHMbZ4murBUsQq+XT8q8Sldxl7IqPcSEKzLaW+JNMyZ4nX/njAR
+A9cTJDTWp38EqxUdzo1ZifVMZBeI+xIyqOuyfp/zUpaoeGbCuGQHJ8A7WHagQkh9
+x1gjOtlIdFuX2scSHrhbbsccmIPJJ18MquM8SnauF2cW9oLo/8n0dMmaXJFqb5sK
+gjitPcy+usA6e8ZquwWuDheSuEo5p/S7ti02hGhWTzCWvq/nnZnEvUp642LKcIYI
+8oJxXAFEGPm3HaucWGPNuqECAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/script/main.lua b/script/main.lua
index 11da6b6f..df91a32d 100644
--- a/script/main.lua
+++ b/script/main.lua
@@ -1,77 +1,106 @@
local httpd = require "httpd"
-local http = require "httpd.http"
local httpc = require "httpc"
-
-
+local DB = require "DB"
+
+--[[
+请按照以下步奏初始化后台:
+ 1. 创建一个数据库(名字任意);
+ 2. 请手动打开lualib/db/database.sql文件, 复制里面的SQL语句在GUI工具中执行一次;
+ 3. 执行完成之后, 将您填写的数据库替换database字段, 并且charset需要设置一致.
+]]
+
+local db = DB:new({
+ host = 'localhost',
+ port = 3306,
+ username = 'root',
+ password = '123456789',
+ charset = 'utf8mb4',
+ database = 'cfadmin',
+ max = 100,
+})
+
+db:connect()
+
+-- 导入httpd对象
local app = httpd:new("App")
+-- httpd启用Cookie扩展
+app:enable_cookie()
+-- httpd设置Cookie加密的密匙
+app:cookie_secure("https://github.com/CandyMi/core_framework")
+-- app:cookie_secure("candymi")
+app:ws('/ws', require "ws")
+
+app:api('/api', function (content)
+ local code, response = httpc.get("https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=13000000000")
+ return code == 200 and response or "httc请求失败"
+end)
--- 每次http请求在处理函数之前将调用此方法, 第三方中间件可以在此针对回调函数设计.
--- 需要注意的是: 如果路由不存在, 则不会经过此回调!
--- 以下为使用示例:
--- 如果未使用before回调进行中间件设计或进行header验证, 请不要注册before回调
--- 只要注册了before回调, 即使不做任何操作(返回非法值(nil))也会返回http 401 code.
-app:before(function (content)
- if true then
- return http.ok()
- end
- -- if true then
- -- return http.redirect('https://github.com/CandyMi/core_framework')
- -- end
- -- if true then
- -- return http.throw(431, '
This is 413 Error, too long request header
')
- -- end
+app:use('/view', function (content)
+ return "