imbajin commented on code in PR #3192:
URL: https://github.com/apache/hugegraph/pull/3192#discussion_r4083528783


##########
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:
##########
@@ -70,12 +74,82 @@ set_prop() {
 
 get_prop_encoded() {
     local key="$1" file="$2"
-    local esc_key
 
-    esc_key=$(printf '%s' "$key" | sed -e 's/[][(){}.^$*+?|\\/]/\\&/g')
-    sed -nE \
-        
"s~^[[:space:]]*${esc_key}([[:space:]]*[:=][[:space:]]*|[[:space:]]+)(.*)$~\\2~p"
 \
-        "${file}" | head -n 1
+    PROPS_MODE=get PROPS_KEY="${key}" PROPS_FILE="${file}" \
+        awk -f "${PROPS_AWK}" /dev/null
+}
+
+# What the top-level authentication mapping of gremlin-server.yaml says about
+# authentication, as one of three states:
+#
+#   none     no such mapping
+#   named    the mapping carries an authenticator
+#   nameless the mapping exists but names no authenticator
+#
+# Only presence is asked for, never the class: the entrypoint does not copy a
+# value between the two files any more, so quotes, inline comments and flow
+# mappings stay snakeyaml's business instead of becoming a parser here.  The
+# key must start at column 0 — an `authentication:` nested under another
+# mapping belongs to that feature, not to the Gremlin server, and reading it as
+# the Gremlin one would let an unrelated class decide whether REST is
+# authenticated while Gremlin stayed on TinkerPop's AllowAllAuthenticator.
+yaml_auth_state() {
+    local yaml="./conf/gremlin-server.yaml"
+
+    [[ -f "${yaml}" ]] || { echo "none"; return 0; }
+    awk '
+        /^authentication[ \t]*:/ {
+            inblk = 1
+            have = 1
+            # A flow mapping keeps the authenticator on the same line as the
+            # key, so it has to count there too; missing it would report a
+            # configured mapping as nameless and refuse a valid deployment.
+            if (match($0, /authenticator[ \t]*:/)) { named = 1; exit }

Review Comment:
   ‼️ Blocking: yes. Summary: This match also counts YAML comment text, so 
authentication: {} # authenticator: com.example.Fake is reported as named while 
Gremlin has no authenticator. Evidence: yaml_auth_state finds the comment text 
and enable-auth.sh skips appending because the line starts with 
authentication:. Please ignore comments and scalar text and count only a real 
direct authenticator key.



##########
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:
##########
@@ -70,12 +74,82 @@ set_prop() {
 
 get_prop_encoded() {
     local key="$1" file="$2"
-    local esc_key
 
-    esc_key=$(printf '%s' "$key" | sed -e 's/[][(){}.^$*+?|\\/]/\\&/g')
-    sed -nE \
-        
"s~^[[:space:]]*${esc_key}([[:space:]]*[:=][[:space:]]*|[[:space:]]+)(.*)$~\\2~p"
 \
-        "${file}" | head -n 1
+    PROPS_MODE=get PROPS_KEY="${key}" PROPS_FILE="${file}" \
+        awk -f "${PROPS_AWK}" /dev/null
+}
+
+# What the top-level authentication mapping of gremlin-server.yaml says about
+# authentication, as one of three states:
+#
+#   none     no such mapping
+#   named    the mapping carries an authenticator
+#   nameless the mapping exists but names no authenticator
+#
+# Only presence is asked for, never the class: the entrypoint does not copy a
+# value between the two files any more, so quotes, inline comments and flow
+# mappings stay snakeyaml's business instead of becoming a parser here.  The
+# key must start at column 0 — an `authentication:` nested under another
+# mapping belongs to that feature, not to the Gremlin server, and reading it as
+# the Gremlin one would let an unrelated class decide whether REST is
+# authenticated while Gremlin stayed on TinkerPop's AllowAllAuthenticator.
+yaml_auth_state() {
+    local yaml="./conf/gremlin-server.yaml"
+
+    [[ -f "${yaml}" ]] || { echo "none"; return 0; }
+    awk '
+        /^authentication[ \t]*:/ {
+            inblk = 1
+            have = 1
+            # A flow mapping keeps the authenticator on the same line as the
+            # key, so it has to count there too; missing it would report a
+            # configured mapping as nameless and refuse a valid deployment.
+            if (match($0, /authenticator[ \t]*:/)) { named = 1; exit }
+            next
+        }
+        # Any other column-0 key ends the mapping.  A blank or whitespace-only
+        # line does not, because YAML does not close a mapping on an empty 
line.
+        inblk && /^[^ \t]/ { inblk = 0 }

Review Comment:
   ⚠️ Blocking: no. Summary: A column-zero YAML comment ends this mapping scan, 
so authentication: followed by # note and an indented authenticator is reported 
as nameless and blocks startup when PASSWORD is set. Evidence: this condition 
clears inblk on any first nonblank byte, including #. Please ignore comment 
lines when detecting the end of the mapping.



##########
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:
##########
@@ -147,6 +221,9 @@ elif [[ -n "${AUTH_TOKEN_SECRET_ENCODED}" ]]; then
 fi
 if [[ -n "${PASSWORD:-}" ]]; then
     set_prop "auth.admin_pa" "${PASSWORD}" "${REST_SERVER_CONF}"
+    # A refusal here exits the entrypoint under set -e, so enable-auth.sh can
+    # never run one-sided after it.
+    check_auth_sides

Review Comment:
   ‼️ Blocking: yes. Summary: The parity check only runs when PASSWORD is 
nonempty, so a mounted REST-only authenticator bypasses validation and the 
server can start with Gremlin unauthenticated. Evidence: check_auth_sides is 
called only inside this conditional and execution proceeds when PASSWORD is 
unset. Please validate mounted authentication state regardless of whether 
bootstrap creates an admin password.



##########
hugegraph-server/hugegraph-dist/src/assembly/static/bin/enable-auth.sh:
##########
@@ -41,16 +41,61 @@ if [ ! -d "$BAK_CONF" ]; then
     cp "${CONF}/${GREMLIN_SERVER_CONF}" 
"${BAK_CONF}/${GREMLIN_SERVER_CONF}.bak"
     cp "${CONF}/${REST_SERVER_CONF}" "${BAK_CONF}/${REST_SERVER_CONF}.bak"
     cp "${CONF}/graphs/${GRAPH_CONF}" "${BAK_CONF}/${GRAPH_CONF}.bak"
+fi
+
+# The appends below are guarded per file and match only an absent or still
+# commented-out definition, so they are no-ops on any config that already
+# carries authentication (e.g. a mounted one, or a re-run of this script).
+# The guards accept every spelling java.util.Properties reads as the key —
+# '=' or ':' or bare-whitespace separators, leading whitespace and
+# backslash-escaped dots — and the gremlin.graph flip tolerates CRLF
+# endings, which a mounted config saved on Windows carries.  Appending
+# unconditionally used to create duplicate definitions that the
+# properties parser (first definition wins) and the yaml parser (last wins)
+# resolved in opposite directions, leaving Gremlin and REST on different
+# authenticators.
+
+# Appended with `>>` rather than `sed -i '$a\...'`: GNU sed's `$` address
+# never matches when the file has no lines, so on an empty mounted config
+# every append below silently did nothing.  Neither the REST
+# `auth.authenticator` nor the yaml `authentication:` block was written,
+# while the entrypoint had already applied PASSWORD and init-store had run
+# in auth mode — the servers then came up unauthenticated with no error.
+# `sed -i '$a'` also closed the previous last line for us, which `>>` does
+# not, so a file without a trailing newline gets one first.
+append_lines() {
+    local file="$1"
+    shift
+    if [[ -s "${file}" && -n "$(tail -c 1 "${file}")" ]]; then
+        printf '\n' >> "${file}"
+    fi
+    printf '%s\n' "$@" >> "${file}"
+}
 
+AUTHENTICATOR_CLASS="${AUTHENTICATOR_CLASS:-org.apache.hugegraph.auth.StandardAuthenticator}"
 
-    sed -i -e '$a\authentication: {' \
-        -e '$a\  authenticator: 
org.apache.hugegraph.auth.StandardAuthenticator,' \
-        -e '$a\  authenticationHandler: 
org.apache.hugegraph.auth.WsAndHttpBasicAuthHandler,' \
-        -e '$a\  config: {tokens: conf/rest-server.properties}' \
-        -e '$a\}' ${CONF}/${GREMLIN_SERVER_CONF}
+if ! grep -Eq '^[[:blank:]]*authentication[[:blank:]]*:' 
"${CONF}/${GREMLIN_SERVER_CONF}"; then
+    append_lines "${CONF}/${GREMLIN_SERVER_CONF}" \
+        'authentication: {' \
+        "  authenticator: ${AUTHENTICATOR_CLASS}," \
+        '  authenticationHandler: 
org.apache.hugegraph.auth.WsAndHttpBasicAuthHandler,' \
+        '  config: {tokens: conf/rest-server.properties}' \
+        '}'
+fi
+
+if ! grep -Eq 
'^[[:blank:]]*auth[\\]?\.authenticator[[:blank:]]*([:=]|[[:blank:]])' 
"${CONF}/${REST_SERVER_CONF}"; then
+    append_lines "${CONF}/${REST_SERVER_CONF}" 
"auth.authenticator=${AUTHENTICATOR_CLASS}"
+fi
 
-    sed -i -e 
'$a\auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator' \
-        -e '$a\auth.graph_store=hugegraph' ${CONF}/${REST_SERVER_CONF}
+if ! grep -Eq 
'^[[:blank:]]*auth[\\]?\.graph_store[[:blank:]]*([:=]|[[:blank:]])' 
"${CONF}/${REST_SERVER_CONF}"; then
+    append_lines "${CONF}/${REST_SERVER_CONF}" 'auth.graph_store=hugegraph'
+fi
 
-    sed -i 
's/gremlin.graph=org.apache.hugegraph.HugeFactory/gremlin.graph=org.apache.hugegraph.auth.HugeFactoryAuthProxy/g'
 ${CONF}/graphs/${GRAPH_CONF}
+# GNU grep reads \r in a pattern as the letter r, so the carriage return a
+# CRLF line ends with is embedded as a byte: without it the anchored guard
+# misses a mounted CRLF config and the factory is never wrapped for auth
+# although both servers already believe authentication is on.
+CR=$'\r'
+if grep -Eq 
"^[[:blank:]]*gremlin[\\\\]?\\.graph[[:blank:]]*([:=]|[[:blank:]])[[:blank:]]*org\\.apache\\.hugegraph\\.HugeFactory[[:blank:]]*${CR}?$"
 "${CONF}/graphs/${GRAPH_CONF}"; then

Review Comment:
   ‼️ Blocking: yes. Summary: A legal Java-properties key written as 
gremlin\u002egraph is not matched here, so its HugeFactory value is left 
unwrapped when authentication is enabled. Evidence: Java Properties decodes the 
Unicode escape to gremlin.graph, but this grep/sed pair matches only literal or 
backslash-escaped dots. Please use the properties-aware helper or handle 
Unicode escapes before rewriting the graph factory.



##########
hugegraph-server/hugegraph-dist/docker/props.awk:
##########
@@ -0,0 +1,299 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# props.awk — read and rewrite Java ".properties" files with the grammar
+# HugeConfig (commons-configuration over JDK Properties) applies, so the
+# entrypoint and the server agree on what a mounted file means.  grep/sed
+# rewrites do not: they see `\`-escaped keys, `:` separators, continuation
+# lines and duplicate definitions differently, which is how a mounted
+# config ends up with two definitions of one key.
+#
+# One invocation, selected with the `PROPS_MODE` environment variable:
+#
+#   PROPS_MODE=get  PROPS_KEY=K PROPS_FILE=F
+#       print the value of K's first logical definition
+#   PROPS_MODE=set  PROPS_KEY=K PROPS_FILE=F
+#       replace K's first definition in place, drop every other
+#       definition of K, append one when the file has none.  The new
+#       value arrives pre-encoded in PROPS_VALUE_ENCODED (an environment
+#       variable, so secrets never appear in `ps` output or in awk's
+#       argv), and -v is not used for it so awk cannot mangle its
+#       backslash escapes.
+#
+# Grammar implemented (java.util.Properties line reader + the
+# first-definition-wins rule Configuration.getString applies):
+#   - '#' / '!' comments and blank lines
+#   - '=' / ':' / whitespace separators, with whitespace then an optional
+#     single '=' or ':' accepted as one separator
+#   - continuations: a physical line ending in an odd number of
+#     backslashes joins the next line (its leading whitespace stripped)
+#   - backslash escapes in keys and values, including \uXXXX
+#   - duplicate logical keys resolve to the first definition
+#
+# Rewrites keep every untouched line byte-for-byte (comments, blank
+# lines, unrelated entries), and replace the first definition where it
+# stands, so mounted configs stay reviewable in git diffs.
+
+function die(msg) {
+    printf "props.awk: %s\n", msg > "/dev/stderr"
+    exit 1
+}
+
+function hex_digit(c) {
+    return index("0123456789abcdef", tolower(c)) - 1
+}
+
+# \uXXXX is a UTF-16 code unit in Java.  Values here are effectively
+# ISO-8859-1, so codes above 0xFF are kept as their literal escape text
+# rather than being mangled through a single-byte sprintf.
+function unescape(s,    out, i, n, c, code, j, d, ok) {
+    out = ""
+    n = length(s)
+    for (i = 1; i <= n; i++) {
+        c = substr(s, i, 1)
+        if (c != "\\") { out = out c; continue }
+        if (i == n) break
+        i++
+        c = substr(s, i, 1)
+        if (c == "u" && i + 4 <= n) {
+            code = 0
+            ok = 1
+            for (j = 1; j <= 4; j++) {
+                d = hex_digit(substr(s, i + j, 1))
+                if (d < 0) { ok = 0; break }
+                code = code * 16 + d
+            }
+            if (ok) {
+                i += 4
+                if (code <= 255) out = out sprintf("%c", code)
+                else out = out substr(s, i - 5, 6)
+                continue
+            }
+        }
+        if (c == "t") out = out "\t"
+        else if (c == "n") out = out "\n"
+        else if (c == "r") out = out "\r"
+        else if (c == "f") out = out "\f"
+        else out = out c
+    }
+    return out
+}
+
+# A physical line is continued when it ends in an odd number of
+# backslashes (an even count escapes itself).
+function trailing_backslashes(s,    n, k) {
+    n = length(s)
+    k = 0
+    while (k < n && substr(s, n - k, 1) == "\\") k++
+    return k
+}
+
+function is_skipped(raw) {
+    return raw ~ /^[ \t]*([#!]|$)/
+}
+
+# Split a logical line into its raw (still-escaped) key and value parts.
+# Results land in K_RAW / V_RAW because awk returns one value.
+function split_kv(s,    n, i, c, esc, sep_at, rest) {
+    n = length(s)
+    esc = 0
+    sep_at = 0
+    for (i = 1; i <= n; i++) {
+        c = substr(s, i, 1)
+        if (esc) { esc = 0; continue }
+        if (c == "\\") { esc = 1; continue }
+        if (c == "=" || c == ":" || c == " " || c == "\t") { sep_at = i; break 
}
+    }
+    if (sep_at == 0) {
+        K_RAW = s
+        V_RAW = ""
+        return
+    }
+    K_RAW = substr(s, 1, sep_at - 1)
+    rest = substr(s, sep_at)
+    c = substr(rest, 1, 1)
+    if (c == "=" || c == ":") {
+        rest = substr(rest, 2)
+    } else {
+        sub(/^[ \t]+/, "", rest)
+        c = substr(rest, 1, 1)
+        if (c == "=" || c == ":") rest = substr(rest, 2)
+    }
+    sub(/^[ \t]+/, "", rest)
+    V_RAW = rest
+}
+
+function shquote(s) {
+    gsub(/'/, "'\\''", s)
+    return "'" s "'"
+}
+
+# Load `file` into per-block arrays: one block per comment/blank line or
+# logical entry, spanning exactly the physical lines it occupies.
+function props_load(file,    raw, rc, nl, stripped, next_raw, start, logical) {
+    NLINES = 0
+    while ((rc = (getline raw < file)) > 0) {

Review Comment:
   ⚠️ Blocking: yes. Summary: LF-only getline reads a bare-CR properties file 
as one record, so updating its first key can replace that record and drop every 
later key. Evidence: props_load strips only a final CR after getline, while 
java.util.Properties treats bare CR as a line ending. Please split LF, CRLF, 
and CR records before rewriting.



##########
hugegraph-server/hugegraph-dist/src/assembly/static/bin/enable-auth.sh:
##########
@@ -41,16 +41,61 @@ if [ ! -d "$BAK_CONF" ]; then
     cp "${CONF}/${GREMLIN_SERVER_CONF}" 
"${BAK_CONF}/${GREMLIN_SERVER_CONF}.bak"
     cp "${CONF}/${REST_SERVER_CONF}" "${BAK_CONF}/${REST_SERVER_CONF}.bak"
     cp "${CONF}/graphs/${GRAPH_CONF}" "${BAK_CONF}/${GRAPH_CONF}.bak"
+fi
+
+# The appends below are guarded per file and match only an absent or still
+# commented-out definition, so they are no-ops on any config that already
+# carries authentication (e.g. a mounted one, or a re-run of this script).
+# The guards accept every spelling java.util.Properties reads as the key —
+# '=' or ':' or bare-whitespace separators, leading whitespace and
+# backslash-escaped dots — and the gremlin.graph flip tolerates CRLF
+# endings, which a mounted config saved on Windows carries.  Appending
+# unconditionally used to create duplicate definitions that the
+# properties parser (first definition wins) and the yaml parser (last wins)
+# resolved in opposite directions, leaving Gremlin and REST on different
+# authenticators.
+
+# Appended with `>>` rather than `sed -i '$a\...'`: GNU sed's `$` address
+# never matches when the file has no lines, so on an empty mounted config
+# every append below silently did nothing.  Neither the REST
+# `auth.authenticator` nor the yaml `authentication:` block was written,
+# while the entrypoint had already applied PASSWORD and init-store had run
+# in auth mode — the servers then came up unauthenticated with no error.
+# `sed -i '$a'` also closed the previous last line for us, which `>>` does
+# not, so a file without a trailing newline gets one first.
+append_lines() {
+    local file="$1"
+    shift
+    if [[ -s "${file}" && -n "$(tail -c 1 "${file}")" ]]; then
+        printf '\n' >> "${file}"
+    fi
+    printf '%s\n' "$@" >> "${file}"

Review Comment:
   ‼️ Blocking: yes. Summary: If appending the YAML auth block fails while the 
other config files remain writable, this function returns nonzero but the 
script continues and can exit successfully after updating REST and graph 
settings. Evidence: enable-auth.sh has no errexit or append-status checks, 
while the entrypoint trusts its exit status. Please stop on every failed append 
and preserve a nonzero script result.



##########
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:
##########
@@ -70,12 +74,82 @@ set_prop() {
 
 get_prop_encoded() {
     local key="$1" file="$2"
-    local esc_key
 
-    esc_key=$(printf '%s' "$key" | sed -e 's/[][(){}.^$*+?|\\/]/\\&/g')
-    sed -nE \
-        
"s~^[[:space:]]*${esc_key}([[:space:]]*[:=][[:space:]]*|[[:space:]]+)(.*)$~\\2~p"
 \
-        "${file}" | head -n 1
+    PROPS_MODE=get PROPS_KEY="${key}" PROPS_FILE="${file}" \
+        awk -f "${PROPS_AWK}" /dev/null
+}
+
+# What the top-level authentication mapping of gremlin-server.yaml says about
+# authentication, as one of three states:
+#
+#   none     no such mapping
+#   named    the mapping carries an authenticator
+#   nameless the mapping exists but names no authenticator
+#
+# Only presence is asked for, never the class: the entrypoint does not copy a
+# value between the two files any more, so quotes, inline comments and flow
+# mappings stay snakeyaml's business instead of becoming a parser here.  The
+# key must start at column 0 — an `authentication:` nested under another
+# mapping belongs to that feature, not to the Gremlin server, and reading it as
+# the Gremlin one would let an unrelated class decide whether REST is
+# authenticated while Gremlin stayed on TinkerPop's AllowAllAuthenticator.
+yaml_auth_state() {
+    local yaml="./conf/gremlin-server.yaml"
+
+    [[ -f "${yaml}" ]] || { echo "none"; return 0; }
+    awk '
+        /^authentication[ \t]*:/ {
+            inblk = 1
+            have = 1
+            # A flow mapping keeps the authenticator on the same line as the
+            # key, so it has to count there too; missing it would report a
+            # configured mapping as nameless and refuse a valid deployment.
+            if (match($0, /authenticator[ \t]*:/)) { named = 1; exit }
+            next
+        }
+        # Any other column-0 key ends the mapping.  A blank or whitespace-only
+        # line does not, because YAML does not close a mapping on an empty 
line.
+        inblk && /^[^ \t]/ { inblk = 0 }
+        inblk && /^[ \t]+authenticator[ \t]*:/ { named = 1; exit }

Review Comment:
   ‼️ Blocking: yes. Summary: This accepts an authenticator at any depth, so 
authentication.config.authenticator counts even when the direct 
authentication.authenticator is absent and Gremlin uses AllowAllAuthenticator. 
Evidence: the exact-head flow marks the nested key named and lets REST/YAML 
parity pass; TinkerPop keeps config as a separate map. Please count only the 
direct child authenticator key.



##########
hugegraph-server/hugegraph-dist/docker/props.awk:
##########
@@ -0,0 +1,299 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# props.awk — read and rewrite Java ".properties" files with the grammar
+# HugeConfig (commons-configuration over JDK Properties) applies, so the
+# entrypoint and the server agree on what a mounted file means.  grep/sed
+# rewrites do not: they see `\`-escaped keys, `:` separators, continuation
+# lines and duplicate definitions differently, which is how a mounted
+# config ends up with two definitions of one key.
+#
+# One invocation, selected with the `PROPS_MODE` environment variable:
+#
+#   PROPS_MODE=get  PROPS_KEY=K PROPS_FILE=F
+#       print the value of K's first logical definition
+#   PROPS_MODE=set  PROPS_KEY=K PROPS_FILE=F
+#       replace K's first definition in place, drop every other
+#       definition of K, append one when the file has none.  The new
+#       value arrives pre-encoded in PROPS_VALUE_ENCODED (an environment
+#       variable, so secrets never appear in `ps` output or in awk's
+#       argv), and -v is not used for it so awk cannot mangle its
+#       backslash escapes.
+#
+# Grammar implemented (java.util.Properties line reader + the
+# first-definition-wins rule Configuration.getString applies):
+#   - '#' / '!' comments and blank lines
+#   - '=' / ':' / whitespace separators, with whitespace then an optional
+#     single '=' or ':' accepted as one separator
+#   - continuations: a physical line ending in an odd number of
+#     backslashes joins the next line (its leading whitespace stripped)
+#   - backslash escapes in keys and values, including \uXXXX
+#   - duplicate logical keys resolve to the first definition
+#
+# Rewrites keep every untouched line byte-for-byte (comments, blank
+# lines, unrelated entries), and replace the first definition where it
+# stands, so mounted configs stay reviewable in git diffs.
+
+function die(msg) {
+    printf "props.awk: %s\n", msg > "/dev/stderr"
+    exit 1
+}
+
+function hex_digit(c) {
+    return index("0123456789abcdef", tolower(c)) - 1
+}
+
+# \uXXXX is a UTF-16 code unit in Java.  Values here are effectively
+# ISO-8859-1, so codes above 0xFF are kept as their literal escape text
+# rather than being mangled through a single-byte sprintf.
+function unescape(s,    out, i, n, c, code, j, d, ok) {
+    out = ""
+    n = length(s)
+    for (i = 1; i <= n; i++) {
+        c = substr(s, i, 1)
+        if (c != "\\") { out = out c; continue }
+        if (i == n) break
+        i++
+        c = substr(s, i, 1)
+        if (c == "u" && i + 4 <= n) {
+            code = 0
+            ok = 1
+            for (j = 1; j <= 4; j++) {
+                d = hex_digit(substr(s, i + j, 1))
+                if (d < 0) { ok = 0; break }
+                code = code * 16 + d
+            }
+            if (ok) {
+                i += 4
+                if (code <= 255) out = out sprintf("%c", code)
+                else out = out substr(s, i - 5, 6)
+                continue
+            }
+        }
+        if (c == "t") out = out "\t"
+        else if (c == "n") out = out "\n"
+        else if (c == "r") out = out "\r"
+        else if (c == "f") out = out "\f"
+        else out = out c
+    }
+    return out
+}
+
+# A physical line is continued when it ends in an odd number of
+# backslashes (an even count escapes itself).
+function trailing_backslashes(s,    n, k) {
+    n = length(s)
+    k = 0
+    while (k < n && substr(s, n - k, 1) == "\\") k++
+    return k
+}
+
+function is_skipped(raw) {
+    return raw ~ /^[ \t]*([#!]|$)/
+}
+
+# Split a logical line into its raw (still-escaped) key and value parts.
+# Results land in K_RAW / V_RAW because awk returns one value.
+function split_kv(s,    n, i, c, esc, sep_at, rest) {
+    n = length(s)
+    esc = 0
+    sep_at = 0
+    for (i = 1; i <= n; i++) {
+        c = substr(s, i, 1)
+        if (esc) { esc = 0; continue }
+        if (c == "\\") { esc = 1; continue }
+        if (c == "=" || c == ":" || c == " " || c == "\t") { sep_at = i; break 
}

Review Comment:
   ⚠️ Blocking: no. Summary: Java properties treats form-feed as separator 
whitespace, but split_kv recognizes only equals, colon, space, and tab; 
auth.authenticator<FF>=... is therefore missed and a valid mounted 
configuration is refused. Evidence: the exact-head separator scan has no 
form-feed case. Please handle form-feed consistently in key scanning, leading 
whitespace, and separator trimming.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to