@@ -25,19 +25,18 @@ resulting from the use or misuse of this software.
2525package security
2626
2727import (
28- "bufio"
2928 "errors"
3029 "fmt"
3130 "io/fs"
3231 "math/rand/v2" //#nosec
33- "path/filepath "
32+ "path"
3433 "sort"
3534 "strconv"
3635)
3736
3837type SampleLocation struct {
3938 DirStack []int // every index is level and value is dir num
40- FileStack []int // file index, line index, left line border, right line border
39+ FileStack []int // file index
4140}
4241
4342func ResolveChallenge (codebase FileSystem , location SampleLocation , nonce int64 ) ([]byte , error ) {
@@ -60,10 +59,7 @@ func GenerateChallenge(codebase FileSystem, nonce int64) ([]byte, SampleLocation
6059
6160var (
6261 ErrNoSampleFiles = errors .New ("challenge: no usable files or subdirectories" )
63- ErrEmptySampleLine = errors .New ("empty sample line found" )
64- ErrNoNonEmptySampleLines = errors .New ("no non-empty lines found" )
6562 ErrSampleIndexOutOfBounds = errors .New ("sample index out of bounds" )
66- ErrInvalidSubstringBounds = errors .New ("invalid substring bounds" )
6763)
6864
6965func generateSample (codebase FileSystem , dir string , dirStack []int ) (_ string , result SampleLocation , err error ) {
@@ -85,79 +81,37 @@ func generateSample(codebase FileSystem, dir string, dirStack []int) (_ string,
8581 }
8682 }
8783
88- perm := rand .Perm (len (dirs ))
89- for _ , dirIndex := range perm {
90- selectedDir := dirs [dirIndex ]
91- subPath := filepath .Join (dir , selectedDir .Name ())
92-
93- subEntries , err := fs .ReadDir (codebase , subPath )
94- if err != nil {
95- continue
96- }
97-
98- var fileCount int
99- for _ , e := range subEntries {
100- if ! e .IsDir () {
101- fileCount ++
84+ // Subdirectories and the files of this directory compete on equal footing,
85+ // so files outside leaf directories are sampled too.
86+ total := len (dirs ) + len (files )
87+ for _ , c := range rand .Perm (total ) {
88+ if c < len (dirs ) {
89+ subPath := path .Join (dir , dirs [c ].Name ())
90+ sample , subResult , err := generateSample (codebase , subPath , append (dirStack , c ))
91+ if err == nil {
92+ return sample , subResult , nil
10293 }
103- }
104- if fileCount == 0 {
10594 continue
10695 }
10796
108- sample , subResult , err := generateSample (codebase , subPath , append (dirStack , dirIndex ))
109- if err == nil {
110- return sample , subResult , nil
111- }
112- }
113-
114- if len (files ) == 0 {
115- return "" , result , ErrNoSampleFiles
116- }
117-
118- fileIndex := rand .IntN (len (files )) //#nosec
119- selectedFile := files [fileIndex ]
120- fullPath := filepath .Join (dir , selectedFile .Name ())
121-
122- line , lineNum , err := getRandomLine (codebase , fullPath )
123- if err != nil {
124- return "" , result , fmt .Errorf ("challenge: read random line from %s: %w" , fullPath , err )
125- }
126- if len (line ) == 0 {
127- return "" , result , fmt .Errorf ("challenge: %w, path: %s" , ErrEmptySampleLine , fullPath )
128- }
129-
130- lineLen := len (line )
131- var left , right int
132- if lineLen == 1 {
133- left , right = 0 , 1
134- } else {
135- left = rand .IntN (lineLen - 1 ) //#nosec
136- right = left + 1 + rand .IntN (lineLen - left - 1 ) //#nosec
137- }
138-
139- sample := line [left :right ]
97+ fileIndex := c - len (dirs )
98+ fullPath := path .Join (dir , files [fileIndex ].Name ())
14099
141- return sample , SampleLocation {
142- DirStack : dirStack ,
143- FileStack : []int {fileIndex , lineNum , left , right },
144- }, nil
145- }
100+ content , err := fs .ReadFile (codebase , fullPath )
101+ if err != nil || len (content ) == 0 {
102+ continue
103+ }
146104
147- func getRandomLine (codebase FileSystem , path string ) (string , int , error ) {
148- lines , err := readLines (codebase , path )
149- if err != nil {
150- return "" , 0 , err
151- }
152- if len (lines ) == 0 {
153- return "" , 0 , fmt .Errorf ("challenge: %w, path %s" , ErrNoNonEmptySampleLines , path )
105+ return string (content ), SampleLocation {
106+ DirStack : dirStack ,
107+ FileStack : []int {fileIndex },
108+ }, nil
154109 }
155110
156- index := rand .IntN (len (lines )) //#nosec
157- return lines [index ], index , nil
111+ return "" , result , ErrNoSampleFiles
158112}
159113
160- var ErrInvalidStackSize = errors .New ("challenge: invalid file stack size - expected 4 elements " )
114+ var ErrInvalidStackSize = errors .New ("challenge: invalid file stack size - expected at least 1 element " )
161115
162116func findSample (codebase FileSystem , loc SampleLocation ) (string , error ) {
163117 currentDir := "."
@@ -178,12 +132,12 @@ func findSample(codebase FileSystem, loc SampleLocation) (string, error) {
178132 dirs = append (dirs , e )
179133 }
180134 }
181- if dirIndex >= len (dirs ) {
135+ if dirIndex < 0 || dirIndex >= len (dirs ) {
182136 return "" , fmt .Errorf ("challenge: dir index %d: level %d: %w" , dirIndex , level , ErrSampleIndexOutOfBounds )
183137 }
184138
185139 nextDir := dirs [dirIndex ].Name ()
186- currentDir = filepath .Join (currentDir , nextDir )
140+ currentDir = path .Join (currentDir , nextDir )
187141 }
188142
189143 entries , err := fs .ReadDir (codebase , currentDir )
@@ -201,56 +155,22 @@ func findSample(codebase FileSystem, loc SampleLocation) (string, error) {
201155 regularFiles = append (regularFiles , e )
202156 }
203157 }
204- if len (loc .FileStack ) != 4 {
158+ if len (loc .FileStack ) < 1 {
205159 return "" , ErrInvalidStackSize
206160 }
207161
208162 fileIndex := loc .FileStack [0 ]
209- lineIndex := loc .FileStack [1 ]
210- left := loc .FileStack [2 ]
211- right := loc .FileStack [3 ]
212-
213- if fileIndex >= len (regularFiles ) {
163+ if fileIndex < 0 || fileIndex >= len (regularFiles ) {
214164 return "" , fmt .Errorf ("challenge: %d %w - found %d files" , fileIndex , ErrSampleIndexOutOfBounds , len (regularFiles ))
215165 }
216166
217167 targetFile := regularFiles [fileIndex ].Name ()
218- fullPath := filepath .Join (currentDir , targetFile )
168+ fullPath := path .Join (currentDir , targetFile )
219169
220- lines , err := readLines (codebase , fullPath )
170+ content , err := fs . ReadFile (codebase , fullPath )
221171 if err != nil {
222- return "" , fmt .Errorf ("challenge: read lines from %s: %w" , fullPath , err )
223- }
224- if lineIndex >= len (lines ) {
225- return "" , fmt .Errorf ("challenge: %d %w, len=%d" , lineIndex , ErrSampleIndexOutOfBounds , len (lines ))
172+ return "" , fmt .Errorf ("challenge: read file %s: %w" , fullPath , err )
226173 }
227174
228- line := lines [lineIndex ]
229- if left > right || left < 0 || right > len (line ) {
230- return "" , fmt .Errorf ("challenge: %w: [%d:%d] on len=%d" , ErrInvalidSubstringBounds , left , right , len (line ))
231- }
232-
233- return line [left :right ], nil
234- }
235-
236- func readLines (codebase FileSystem , path string ) ([]string , error ) {
237- f , err := codebase .Open (path )
238- if err != nil {
239- return nil , err
240- }
241-
242- var lines []string
243- scanner := bufio .NewScanner (f )
244- for scanner .Scan () {
245- text := scanner .Text ()
246- if len (text ) <= 2 { // drop '}',')', '\t', '\n' etc.
247- continue
248- }
249-
250- lines = append (lines , text )
251- }
252-
253- _ = f .Close ()
254-
255- return lines , scanner .Err ()
175+ return string (content ), nil
256176}
0 commit comments