Repository navigation
install.sh and openRepoShape: a $OPENREPOSHAPE_BIN_DIR, the repository or a ref holding a bidi control, a character of no width, a soft hyphen or U+2028 is refused by its code point before any request and never echoed, so no line shows the value as another
#841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a3cde99
7569ffa
a22b94c
efd061a
caecf88
7e4181c
8a559cb
cc5ae88
4123b9e
eef556b
f2483cf
e7743c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,7 +45,8 @@ | |
| # command already there as a symbolic link (pipx's, #563), a | ||
| # `$OPENREPOSHAPE_BIN_DIR` spelled for Windows where no `cygpath` can | ||
| # spell it the way bash does (#667), or a directory holding a control | ||
| # character or a `:`, which no line about `$PATH` can name (#760). | ||
| # character or a `:`, which no line about `$PATH` can name (#760), or | ||
| # a character a terminal draws as something other than itself (#837). | ||
| # git-filter-repo never changes it: only adopting needs it, and | ||
| # `adopt-project.py` refuses with its own install command where it is missing. | ||
| # What it changes is the summary (#595): `adoption:` says whether it is there | ||
|
|
@@ -122,8 +123,16 @@ USAGE | |
| # THE REPOSITORY, AS AN OWNER/NAME, because every URL below is built from it. | ||
| # The launcher also takes a URL or a path in this variable, for the clone | ||
| # setup.sh makes; there is no release at either, so this refuses them by | ||
| # name. A control character is refused before anything is echoed. | ||
| # name. A control character is refused before anything is echoed, and | ||
| # before it a character a terminal draws as something other than itself | ||
| # (#837), named by its code point: first, because `[![:print:]]` is the | ||
| # locale's, matching every byte past ASCII under `C` and none of those | ||
| # characters under a UTF-8 locale, where the refusal below would echo them. | ||
| checked_repo() { | ||
| local misdrawn | ||
| misdrawn="$(LC_ALL=C; misdrawn_characters "$REPO")" | ||
| [ -z "$misdrawn" ] || | ||
| die "\$OPENREPOSHAPE_REPO holds a character a terminal draws as something other than itself ($misdrawn): one that is invisible, is drawn as another character, ends a line or reorders the text around it. It must be an owner/name on github.com, such as opensoft/openRepoShape." | ||
| case "$REPO" in | ||
| *[![:print:]]*) die "\$OPENREPOSHAPE_REPO carries a control character; it must be an owner/name on github.com, such as opensoft/openRepoShape." ;; | ||
| esac | ||
|
|
@@ -139,14 +148,19 @@ checked_repo() { | |
| # THE LAUNCHER'S `checked_ref`, TRANSCRIBED: the version reaches a URL here | ||
| # and `git checkout` once the command runs, so it is held to the same three | ||
| # checks in the same order (length, then a control character, then the | ||
| # shape, which is the only one that echoes it), plus the empty value. Not | ||
| # shared, because this file arrives alone down a pipe. | ||
| # shape, which is the only one that echoes it), plus the empty value, and, | ||
| # between the length and the control character, a character a terminal | ||
| # draws as something other than itself, as the launcher's `ref_fault` has | ||
| # it (#837). Not shared, because this file arrives alone down a pipe. | ||
| checked_version() { | ||
| local ref="$1" what="$2" | ||
| local ref="$1" what="$2" misdrawn | ||
| [ -n "$ref" ] || die "$what is empty. Name a release, such as v1.0.1, or none for the newest." | ||
| if [ "${#ref}" -gt 255 ]; then | ||
| die "$what is ${#ref} characters long; a release tag, branch or commit is at most 255 characters." | ||
| fi | ||
| misdrawn="$(LC_ALL=C; misdrawn_characters "$ref")" | ||
| [ -z "$misdrawn" ] || | ||
| die "$what holds a character a terminal draws as something other than itself ($misdrawn): one that is invisible, is drawn as another character, ends a line or reorders the text around it. No release tag, branch or commit holds one." | ||
| case "$ref" in | ||
| *[![:print:]]*) die "$what carries a control character, which is not a release tag, branch or commit." ;; | ||
| esac | ||
|
|
@@ -195,10 +209,16 @@ latest_from_redirect() { | |
| return 0 | ||
| ;; | ||
| esac | ||
| case "$value" in | ||
| *[![:print:]]*) FAULT="it redirects to a location that names no release" ;; | ||
| *) FAULT="it redirects to $value, which names no release" ;; | ||
| esac | ||
| # The location is named only when it is text a terminal draws as | ||
| # it is: no control character, and no character it draws as | ||
| # something other than itself (#837). | ||
| FAULT="it redirects to a location that names no release" | ||
| if [ -z "$(LC_ALL=C; misdrawn_characters "$value")" ]; then | ||
| case "$value" in | ||
| *[![:print:]]*) ;; | ||
| *) FAULT="it redirects to $value, which names no release" ;; | ||
| esac | ||
| fi | ||
| return 1 | ||
| ;; | ||
| esac | ||
|
|
@@ -336,8 +356,52 @@ filter_repo_step() { | |
| adoption_not_ready "it is not installed" "$how $found" | ||
| } | ||
|
|
||
| # THE CHARACTERS A TERMINAL DRAWS AS SOMETHING OTHER THAN THEMSELVES, AND | ||
| # WHICH OF THEM "$1" HOLDS (#837): the launcher's `MISDRAWN_CHARACTERS` and | ||
| # `misdrawn_characters`, copied as they are, for the reason the control | ||
| # check in `main` is copied: this file arrives alone down a pipe. The | ||
| # launcher says why these characters and no others, and which forms of | ||
| # UTF-8 the function builds (two, three and four bytes), and | ||
| # `tests/test_openreposhape_command.py` holds the two copies to one text. | ||
| # `main` asks it of the directory, of an option, `checked_repo` of the | ||
| # repository, `checked_version` of a version and `latest_from_redirect` | ||
| # of the location it reads, each before any line prints the value. | ||
| MISDRAWN_CHARACTERS=(00AD 061C 200B 200E-200F 2028-202E 2060-206F FEFF FFF9-FFFB 1D173-1D17A E0001) | ||
|
|
||
| misdrawn_characters() { | ||
| local value="$1" entry code last bytes name found="" | ||
| for entry in "${MISDRAWN_CHARACTERS[@]}"; do | ||
| code=$((0x${entry%-*})) | ||
| last=$((0x${entry#*-})) | ||
| while [ "$code" -le "$last" ]; do | ||
| if [ "$code" -lt 2048 ]; then | ||
| printf -v bytes '\\0%o\\0%o' $((0xC0 | (code >> 6))) \ | ||
| $((0x80 | (code & 63))) | ||
| elif [ "$code" -lt 65536 ]; then | ||
| printf -v bytes '\\0%o\\0%o\\0%o' $((0xE0 | (code >> 12))) \ | ||
| $((0x80 | ((code >> 6) & 63))) $((0x80 | (code & 63))) | ||
| else | ||
| printf -v bytes '\\0%o\\0%o\\0%o\\0%o' \ | ||
| $((0xF0 | (code >> 18))) $((0x80 | ((code >> 12) & 63))) \ | ||
| $((0x80 | ((code >> 6) & 63))) $((0x80 | (code & 63))) | ||
| fi | ||
| printf -v bytes '%b' "$bytes" | ||
| case "$value" in | ||
| *"$bytes"*) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · Valid legacy-encoding names are refused When the directory name is encoded in a multibyte non-UTF-8 locale and its bytes overlap a listed character’s UTF-8 encoding, Decode and classify the value according to an explicitly supported input encoding, or limit this byte-sequence refusal to UTF-8 inputs and document the behavior for other encodings. Prompt for AI agentsIn `install.sh` at line 390:
**Valid legacy-encoding names are refused**
When the directory name is encoded in a multibyte non-UTF-8 locale and its bytes overlap a listed character’s UTF-8 encoding, `misdrawn_characters` matches UTF-8 byte sequences without decoding the input, so a valid character in a multibyte non-UTF-8 locale whose encoding overlaps one of those sequences is reported as a forbidden code point and the installer refuses that directory. The launcher carries the same byte-matching logic, so it refuses the same names.
Decode and classify the value according to an explicitly supported input encoding, or limit this byte-sequence refusal to UTF-8 inputs and document the behavior for other encodings. |
||
| printf -v name 'U+%04X' "$code" | ||
| found="${found:+$found, }$name" | ||
| ;; | ||
| *) ;; | ||
| esac | ||
| code=$((code + 1)) | ||
| done | ||
| done | ||
| printf '%s' "$found" | ||
| } | ||
|
|
||
| main() { | ||
| local version="" asked launcher_url bin_dir spelled target shown status=0 pinned=1 | ||
| local misdrawn | ||
| local machine="this machine is ready" | ||
|
|
||
| # NOTHING A CHILD READS IS THIS SCRIPT. Piped, stdin is the pipe the | ||
|
|
@@ -350,6 +414,9 @@ main() { | |
| -h | --help) usage; exit 0 ;; | ||
| --) shift; continue ;; | ||
| -*) | ||
| misdrawn="$(LC_ALL=C; misdrawn_characters "$1")" | ||
| [ -z "$misdrawn" ] || | ||
| die "an option holds a character a terminal draws as something other than itself ($misdrawn): one that is invisible, is drawn as another character, ends a line or reorders the text around it. The one argument is a version, such as v1.0.1; with none, the newest release." | ||
| case "$1" in | ||
| *[![:print:]]*) die "an option carries a control character. The one argument is a version, such as v1.0.1; with none, the newest release." ;; | ||
| esac | ||
|
|
@@ -379,7 +446,8 @@ main() { | |
| # print (`café` under LC_ALL=C) is no fault of the directory's. The check | ||
| # runs under `LC_ALL=C` and names U+0080 to U+009F's two bytes (`\302`, | ||
| # then `\200` to `\237`), so a C1 control is refused in every locale, | ||
| # whatever else the value holds; U+2028 and U+2029 are refused in none. | ||
| # whatever else the value holds; U+2028 and U+2029, which only a UTF-8 | ||
| # locale's `[[:cntrl:]]` matched, are the next check's (#837). | ||
| # Left to the locale, `[[:cntrl:]]` matches a C1 control only in a value | ||
| # that is valid UTF-8: one byte that is not (a lone 0xE9) makes bash | ||
| # match byte by byte, and then U+009B's two bytes are not controls, as | ||
|
|
@@ -389,6 +457,14 @@ main() { | |
| if (LC_ALL=C; case "$bin_dir" in *[[:cntrl:]]* | *$'\302'[$'\200'-$'\237']*) exit 0 ;; *) exit 1 ;; esac); then | ||
| die "the directory to install into holds a control character, such as a newline, a carriage return, a tab or an escape, and no line about \$PATH can print such a name as it is: a terminal acts on it. Nothing was installed. Set \$OPENREPOSHAPE_BIN_DIR to a directory without one, then run this line again." | ||
| fi | ||
| # A CHARACTER A TERMINAL DRAWS AS SOMETHING OTHER THAN ITSELF IS REFUSED | ||
| # NEXT (#837), as the command refuses it, and before any request: the | ||
| # name would read as another directory's in every line below. The | ||
| # refusal names each one by its code point, because nobody can see it, | ||
| # and says nothing else of the value. | ||
| misdrawn="$(LC_ALL=C; misdrawn_characters "$bin_dir")" | ||
| [ -z "$misdrawn" ] || | ||
| die "the directory to install into holds a character a terminal draws as something other than itself ($misdrawn): one that is invisible, is drawn as another character, ends a line or reorders the text around it, so no line about \$PATH can print the name as it reads. Nothing was installed. Set \$OPENREPOSHAPE_BIN_DIR to a directory without one, then run this line again." | ||
| # A WINDOWS SPELLING IS SPELLED THE WAY BASH SPELLS IT, THE COMMAND'S OWN | ||
| # RULE (#667, after #649), before it is named in a line or looked at: a | ||
| # value typed `D:\tools\bin` was echoed as typed, in the `install:`, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Medium · Stray emoji tags remain invisible
When a binary directory contains emoji tag characters outside a valid subdivision-flag sequence, the byte check excludes the emoji tag characters everywhere, not just inside a valid subdivision-flag sequence. A directory name with tag characters inserted between ordinary letters is therefore accepted even though terminals that ignore those tags display a different name, so the PATH note can still identify the wrong directory.
Recognize and allow tags only as part of a valid subdivision-flag sequence, and refuse tag characters elsewhere.
Prompt for AI agents