Daniel Covington пре 1 недеља
родитељ
комит
cd7e06ba9b
25 измењених фајлова са 979 додато и 2810 уклоњено
  1. +21
    -4
      TESTING.md
  2. +9
    -1
      app/controllers/autoload_controllers.asp
  3. +2
    -2
      app/views/shared/header.asp
  4. +8
    -0
      applicationhost.config
  5. +26
    -1
      core/autoload_core.asp
  6. +35
    -0
      core/helpers.asp
  7. +1
    -1
      core/lib.CDOEmail.asp
  8. +4
    -4
      core/lib.Collections.asp
  9. +65
    -0
      core/lib.ControllerFactory.asp
  10. +79
    -48
      core/lib.Data.asp
  11. +1
    -1
      core/lib.HTML.asp
  12. +252
    -33
      core/lib.crypto.helper.asp
  13. +1
    -0
      core/lib.helpers.asp
  14. +1
    -0
      core/lib.json.asp
  15. +0
    -166
      core/mvc.asp
  16. +320
    -0
      core/mvc.wsc
  17. +0
    -1543
      docs/AGENTS.md
  18. +0
    -994
      docs/CLAUDE.md
  19. +36
    -3
      public/Default.asp
  20. +13
    -1
      run_site.cmd
  21. +1
    -1
      tests/component/web.config
  22. +101
    -4
      tests/integration/TestMvcDispatch.asp
  23. +1
    -1
      tests/integration/web.config
  24. +1
    -1
      tests/unit/web.config
  25. +1
    -1
      tests/web.config

+ 21
- 4
TESTING.md Прегледај датотеку

@@ -43,15 +43,32 @@ The `tests/` IIS application assumes the repository layout keeps `tests/`, `publ

## IIS Setup

### Option A: IIS Express via the checked-in `applicationhost.config` (quickest)

This repo ships an `applicationhost.config` (used by `run_site.cmd`) that defines two IIS Express sites:

- `Development Web Site` - `public/` on `http://localhost:8080/`
- `Tests Web Site` - `tests/` on `http://localhost:8081/`

`<asp enableParentPaths="true" .../>` is set server-wide in that same file - this is what lets `tests/bootstrap.asp` and the integration pages `#include` sibling files from `../core/` and `../app/` (physically outside the `tests/` site's own root). No per-app IIS configuration is required.

A single `iisexpress.exe` process only ever runs **one** site from a config file - per `iisexpress /?`: "`/config:config-file` ... runs the first site in the specified configuration file." `serverAutoStart="true"` is an IIS/W3SVC (full IIS) concept and is not honored by a standalone `iisexpress.exe` invocation, so listing a second `<site>` is not enough on its own.

1. Run `run_site.cmd` - it launches **two** `iisexpress.exe` processes, each pinned to one site with `/site:"..."` (each opens its own console window so you can see that site's request log).
2. Browse to `http://localhost:8081/` (or run `tests\run-tests.cmd`, which syncs configs first, then opens the runner URL).
3. `tests/web.config`'s `ProductionAppBaseUrl` is set to `http://localhost:8080/` to match the production site's actual binding above - `TestRenderedOutput.asp`/`TestSharedLayout.asp` use this to fetch real rendered pages over HTTP. If you rebind either site to a different port, update this value too, update the matching `/site:`/binding in `applicationhost.config` and `run_site.cmd`, and re-run the sync script (step 5 below).

If `applicationhost.config` only has the `Development Web Site` entry (e.g. in a checkout from before the `Tests Web Site` entry was added), that's why the tests app has nothing to browse to - add a second `<site>` block for `tests/` following the same shape as the existing one, on its own port.

### Option B: A real IIS server

1. Keep the existing production IIS app rooted at `public/`.
2. Create a separate development-only IIS application rooted at `tests/`.
3. Enable Classic ASP for that IIS app.
4. Ensure parent paths are allowed for the `tests/` app. This repo ships `tests/web.config` with `enableParentPaths="true"` because the bootstrap and integration pages include sibling files from `../core/` and `../app/`.
4. Ensure parent paths are allowed for the `tests/` app - in IIS Manager this is the "Enable Parent Paths" checkbox under ASP settings for that application (or `<system.webServer><asp enableParentPaths="true" /></system.webServer>` in that app's own web.config). `tests/web.config` does not set this itself; on IIS Express it comes from the server-wide `<asp>` element in `applicationhost.config` instead (see Option A).
5. Browse to the `tests/` app root or directly to `run-all.asp`.
6. If you change `tests/web.config`, run `cscript //nologo tests\sync-webconfigs.vbs` to refresh the nested copies used by the unit, component, and integration pages.
7. If your production app is not served from the same host root as the `tests/` app, set `ProductionAppBaseUrl` in `tests/web.config` and re-run the sync script so rendered-output tests know where to send HTTP requests.
Example: `http://localhost/` for a root site, or `http://localhost/MyClassicApp/` for a virtual-directory app.
8. To sync configs and open the suite in one step on Windows, run `tests\run-tests.cmd` with an optional runner URL argument.
7. Set `ProductionAppBaseUrl` in `tests/web.config` to wherever the production app actually resolves (e.g. `http://localhost/` for a root site, or `http://localhost/MyClassicApp/` for a virtual-directory app) and re-run the sync script so rendered-output tests know where to send HTTP requests.

Example layout:



+ 9
- 1
app/controllers/autoload_controllers.asp Прегледај датотеку

@@ -1,2 +1,10 @@
<!--#include file="HomeController.asp" -->
<!--#include file="ErrorController.asp" -->
<!--#include file="ErrorController.asp" -->
<%
' Register each controller's own singleton accessor with the shared
' ControllerFactory, by reference (GetRef), so the MVC dispatcher (a WSC,
' which cannot see these factory functions directly) never has to Execute/
' Eval a route-supplied string to resolve a controller class.
Call ControllerFactory().Register("HomeController", GetRef("HomeController"))
Call ControllerFactory().Register("ErrorController", GetRef("ErrorController"))
%>

+ 2
- 2
app/views/shared/header.asp Прегледај датотеку

@@ -5,9 +5,9 @@ Response.CodePage = 65001

' Safe title resolution
Dim pageTitle
If IsObject(CurrentController) Then
If IsObject(MVC.CurrentController) Then
On Error Resume Next
pageTitle = CurrentController.Title
pageTitle = MVC.CurrentController.Title
If Err.Number <> 0 Then
pageTitle = "RouteKit Classic ASP"
Err.Clear


+ 8
- 0
applicationhost.config Прегледај датотеку

@@ -162,6 +162,14 @@
<binding protocol="http" bindingInformation=":8080:localhost" />
</bindings>
</site>
<site name="Tests Web Site" id="2" serverAutoStart="true">
<application path="/">
<virtualDirectory path="/" physicalPath="%ASPC_STARTER_ROOT%tests" />
</application>
<bindings>
<binding protocol="http" bindingInformation=":8081:localhost" />
</bindings>
</site>
<siteDefaults>
<!-- To enable logging, please change the below attribute "enabled" to "true" -->
<logFile logFormat="W3C" directory="%AppData%\Microsoft\IISExpressLogs" enabled="false" />


+ 26
- 1
core/autoload_core.asp Прегледај датотеку

@@ -1,7 +1,8 @@
<!--#include file="../Core/helpers.asp"-->
<!--#include file="../Core/lib.ErrorHandler.asp"-->
<!--#include file="../Core/lib.ControllerRegistry.asp"-->
<!--#include file="../Core/mvc.asp"-->
<!--#include file="../Core/lib.ControllerFactory.asp"-->
<!--#include file="../app/Controllers/autoload_controllers.asp"-->
<!--#include file="../Core/lib.DAL.asp"-->
<!--#include file="../Core/lib.Data.asp"-->
<!--#include file="../Core/lib.Migrations.asp"-->
@@ -21,3 +22,27 @@
<!--#include file="../Core/lib.Enumerable.asp"-->
<!--#include file="../Core/lib.ad.auth.asp"-->
<!--#include file="../Core/databaseConnection.asp"-->
<%
' Response cache headers (moved from mvc.asp - now that MVC is a Windows
' Script Component it cannot hold loose top-level ASP statements like this).
Dim cacheYear : cacheYear = GetAppSetting("CacheExpirationYear")
If cacheYear = "nothing" Then cacheYear = "2030"
Response.ExpiresAbsolute = "01/01/" & cacheYear
Response.AddHeader "pragma", "no-cache"
Response.AddHeader "cache-control", "private, no-cache, must-revalidate"

' MVC dispatcher (Windows Script Component). It runs in its own isolated
' script engine, so its dependencies are injected explicitly here rather
' than it reaching into this page's globals itself - see AGENTS.md
' section 69/70 ("Dependencies Must Be Injected").
Dim MVC_Instance
Set MVC_Instance = GetObject("script:" & Server.MapPath("../Core/mvc.wsc") & "")
Set MVC_Instance.Router = router
Set MVC_Instance.ControllerRegistry = ControllerRegistry()
Set MVC_Instance.ControllerFactory = ControllerFactory()
Set MVC_Instance.Routes = Routes()

Function MVC()
Set MVC = MVC_Instance
End Function
%>

+ 35
- 0
core/helpers.asp Прегледај датотеку

@@ -1,5 +1,8 @@
<%
Dim m_indent

Function QuoteValue(val)
Dim conn
if IsWrappedInSingleQuotes(val) then
QuoteValue = val
Exit Function
@@ -92,6 +95,38 @@ Function IIf(condition, trueValue, falseValue)
On Error GoTo 0
End Function

'-----------------------------
' Utility: MVC Dispatch Error Renderer
'
' Renders the error box for a failed MVC.Resolve()/ExecuteAction() call.
' errInfo is the Dictionary from MVC.LastError: Type = "Security" (format/
' whitelist failures - message is safe to show in any environment) or
' Type = "Action" (a controller action raised an error - full detail only
' in development). Consolidates what were four duplicated inline HTML
' blocks in the pre-WSC mvc.asp into one place, matching AGENTS.md's
' "Shared Utilities" rule against duplicate near-identical helpers.
'-----------------------------
Public Sub RenderMvcError(errInfo)
Dim isDevelopment
isDevelopment = (LCase(GetAppSetting("Environment")) = "development")

Response.Write "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; border-radius:4px;'>"

If errInfo("Type") = "Security" Then
Response.Write "<strong>Security Error:</strong> " & Server.HTMLEncode(errInfo("Message"))
ElseIf isDevelopment Then
Response.Write "<strong>Controller Action Error</strong><br>"
Response.Write "Action: <code>" & Server.HTMLEncode(errInfo("ActionName")) & "</code><br>"
Response.Write "Error: " & Server.HTMLEncode(errInfo("Description")) & "<br>"
Response.Write "Error Number: " & errInfo("Number")
Else
Response.Write "<strong>An error occurred</strong><br>"
Response.Write "Please contact the system administrator if the problem persists."
End If

Response.Write "</div>"
End Sub

'-----------------------------
' Utility: Generic Error Reporter
'-----------------------------


+ 1
- 1
core/lib.CDOEmail.asp Прегледај датотеку

@@ -113,7 +113,7 @@ Class CDOEmail_Class
End If

' Add attachments if any
Dim i
Dim i, errNum, errDesc
For i = LBound(arrAttachments) To UBound(arrAttachments)
msg.AddAttachment arrAttachments(i)
Next


+ 4
- 4
core/lib.Collections.asp Прегледај датотеку

@@ -374,8 +374,8 @@ Class LinkedList_Class
End Function

Public Function TO_Array()
Dim i, iter
Dim i, iter, retval
ReDim retval(Me.Count - 1)
i = 0
Set iter = Me.Iterator
@@ -491,8 +491,8 @@ Class DynamicArray_Class
If e < s Then
Set Slice = DynamicArray()
Else
Dim retval, i, j
ReDim retval(e - s)
Dim i, j
j = 0
For i = s to e
Assign retval(j), m_data(i)
@@ -515,7 +515,7 @@ Class DynamicArray_Class
End Function
Public Function TO_Array()
Dim i
Dim i, retval
ReDim retval(m_size - 1)
For i = 0 to UBOUND(retval)
Assign retval(i), m_data(i)


+ 65
- 0
core/lib.ControllerFactory.asp Прегледај датотеку

@@ -0,0 +1,65 @@
<%
'=======================================================================================================================
' Controller Factory
'
' Centralizes controller instantiation by name so the MVC dispatcher (a WSC,
' with its own isolated script engine) never has to Execute/Eval a string
' built from a route-supplied controller name to resolve a class. Each
' controller factory function (e.g. HomeController(), which already exists as
' each controller's own singleton accessor) is registered once, by reference,
' via GetRef - not by re-deriving it from a string at dispatch time.
'=======================================================================================================================

Class ControllerFactory_Class
Private m_factories

Private Sub Class_Initialize()
Set m_factories = Server.CreateObject("Scripting.Dictionary")
m_factories.CompareMode = 1 ' vbTextCompare, case-insensitive
End Sub

Private Sub Class_Terminate()
Set m_factories = Nothing
End Sub

'---------------------------------------------------------------------------------------------------------------------
' Register a controller's factory function (its own singleton accessor,
' e.g. GetRef("HomeController")) under its route name.
'---------------------------------------------------------------------------------------------------------------------
Public Sub Register(controllerName, factoryFunctionRef)
Dim key : key = LCase(Trim(controllerName))
If Not m_factories.Exists(key) Then
m_factories.Add key, factoryFunctionRef
End If
End Sub

'---------------------------------------------------------------------------------------------------------------------
' Create(controllerName) -> the controller singleton instance
'---------------------------------------------------------------------------------------------------------------------
Public Function Create(controllerName)
Dim key, fn
key = LCase(Trim(controllerName))

If Not m_factories.Exists(key) Then
Err.Raise vbObjectError + 1100, "ControllerFactory.Create", "No factory registered for controller: " & controllerName
End If

Set fn = m_factories(key)
Set Create = fn()
End Function

'---------------------------------------------------------------------------------------------------------------------
Public Function IsRegistered(controllerName)
IsRegistered = m_factories.Exists(LCase(Trim(controllerName)))
End Function
End Class

' Singleton instance
Dim ControllerFactory_Class__Singleton
Function ControllerFactory()
If IsEmpty(ControllerFactory_Class__Singleton) Then
Set ControllerFactory_Class__Singleton = New ControllerFactory_Class
End If
Set ControllerFactory = ControllerFactory_Class__Singleton
End Function
%>

+ 79
- 48
core/lib.Data.asp Прегледај датотеку

@@ -2,112 +2,143 @@
Class Database_Class
Private m_connection
Private m_connection_string
Private m_shape_connections

Private m_trace_enabled
Public Sub set_trace(bool) : m_trace_enabled = bool : End Sub
Public Property Get is_trace_enabled : is_trace_enabled = m_trace_enabled : End Property

Private Sub Class_Initialize
Set m_shape_connections = Server.CreateObject("Scripting.Dictionary")
End Sub

'---------------------------------------------------------------------------------------------------------------------
Public Sub Initialize(connection_string)
m_connection_string = connection_string
End Sub

'---------------------------------------------------------------------------------------------------------------------
Public Function ShapeQuery(sql,params)
dim shapeConn : set shapeConn = server.createobject("adodb.connection")
shapeConn.ConnectionString = "Provider=MSDataShape;Data " & m_connection_string
dim cmd : set cmd = server.createobject("adodb.command")
shapeConn.open
set cmd.ActiveConnection = shapeConn
cmd.CommandText = sql
dim rs
' MSDataShape opens its own dedicated connection per call (it cannot reuse
' the plain Connection() used by Query/PagedQuery). That connection is
' tracked and closed in Class_Terminate rather than left open until the
' object is garbage collected.
Public Function ShapeQuery(sql, params)
Dim shapeConn, cmd, rs, key

Set shapeConn = Server.CreateObject("adodb.connection")
shapeConn.ConnectionString = "Provider=MSDataShape;Data " & m_connection_string
shapeConn.Open

key = m_shape_connections.Count
m_shape_connections.Add key, shapeConn

Set cmd = Server.CreateObject("adodb.command")
Set cmd.ActiveConnection = shapeConn
cmd.CommandText = sql

If IsArray(params) then
set rs = cmd.Execute(, params)
Set rs = cmd.Execute(, params)
ElseIf Not IsEmpty(params) then ' one parameter
set rs = cmd.Execute(, Array(params))
Set rs = cmd.Execute(, Array(params))
Else
set rs = cmd.Execute()
Set rs = cmd.Execute()
End If
set ShapeQuery = rs

Set ShapeQuery = rs
End Function

Public Function Query(sql, params)
dim cmd : set cmd = server.createobject("adodb.command")
set cmd.ActiveConnection = Connection
Dim cmd, rs
Set cmd = Server.CreateObject("adodb.command")
Set cmd.ActiveConnection = Connection
cmd.CommandText = sql
dim rs

If IsArray(params) then
set rs = cmd.Execute(, params)
Set rs = cmd.Execute(, params)
ElseIf Not IsEmpty(params) then ' one parameter
set rs = cmd.Execute(, Array(params))
Set rs = cmd.Execute(, Array(params))
Else
set rs = cmd.Execute()
Set rs = cmd.Execute()
End If
set Query = rs
Set Query = rs
End Function
'---------------------------------------------------------------------------------------------------------------------
' per_page/page_num were previously accepted but ignored - PageSize,
' CacheSize, and AbsolutePage were hardcoded to 1 regardless of the
' arguments passed in, so paging never actually worked. page_num is
' clamped to a valid page (1..PageCount) since AbsolutePage raises an
' error outside that range, and honoring the caller's page_num means an
' out-of-range value is now reachable where it previously never was.
Public Function PagedQuery(sql, params, per_page, page_num)
dim cmd : set cmd = server.createobject("adodb.command")
set cmd.ActiveConnection = Connection
Dim cmd, rs
Set cmd = Server.CreateObject("adodb.command")
Set cmd.ActiveConnection = Connection
cmd.CommandText = sql
cmd.CommandType = 1 'adCmdText
cmd.ActiveConnection.CursorLocation = 3 'adUseClient
dim rs

If IsArray(params) then
set rs = cmd.Execute(, params)
Set rs = cmd.Execute(, params)
ElseIf Not IsEmpty(params) then ' one parameter
set rs = cmd.Execute(, Array(params))
Set rs = cmd.Execute(, Array(params))
Else
set rs = cmd.Execute()
Set rs = cmd.Execute()
End If
If Not rs.EOF then
rs.PageSize = 1
rs.CacheSize = 1
rs.AbsolutePage = 1
rs.PageSize = per_page
rs.CacheSize = per_page

If page_num < 1 Then page_num = 1
If page_num > rs.PageCount Then page_num = rs.PageCount
rs.AbsolutePage = page_num
End If
set PagedQuery = rs
Set PagedQuery = rs
End Function
'---------------------------------------------------------------------------------------------------------------------
Public Sub [Execute](sql, params)
me.query sql, params
End Sub
'---------------------------------------------------------------------------------------------------------------------
Public Sub BeginTransaction
Connection.BeginTrans
End Sub
Public Sub RollbackTransaction
Connection.RollbackTrans
End Sub
Public Sub CommitTransaction
Connection.CommitTrans
End Sub
'---------------------------------------------------------------------------------------------------------------------
' Private Methods
'---------------------------------------------------------------------------------------------------------------------
Private Sub Class_terminate
Destroy m_connection

Dim key
If Not m_shape_connections Is Nothing Then
For Each key In m_shape_connections.Keys
Destroy m_shape_connections(key)
Next
Set m_shape_connections = Nothing
End If
End Sub
Public Function Connection
if not isobject(m_connection) then
if not isobject(m_connection) then
set m_connection = Server.CreateObject("adodb.connection")
m_connection.open m_connection_string
end if
set Connection = m_connection
End Function
end Class
%>
%>

+ 1
- 1
core/lib.HTML.asp Прегледај датотеку

@@ -44,7 +44,7 @@ Class HTML_Helper_Class
Public Function LinkToUnless(condition, link_text, controller_name, action_name)
if not condition then
LinkToIf = LinkToExt(link_text, controller_name, action_name, empty, empty)
LinkToUnless = LinkToExt(link_text, controller_name, action_name, empty, empty)
end if
End Function


+ 252
- 33
core/lib.crypto.helper.asp Прегледај датотеку

@@ -1,45 +1,264 @@
<%
'=======================================================================================================================
' Password hashing
'
' Pure VBScript SHA-256 - no external process, no shell-out, no COM crypto
' dependency. The previous implementation shelled out to a PowerShell script
' (hash_sha256.ps1, which did not exist anywhere in this repo) via
' WScript.Shell.Exec with the raw password concatenated directly into the
' command line. That was both non-functional (missing script) and a command
' injection vulnerability (a password containing ", `, ; etc. could execute
' arbitrary PowerShell, and the password was visible in the process command
' line to anything that could list processes).
'
' NOTE: This hashes without a per-user salt, matching the single-argument
' HashPassword(password) contract these functions already had. Unsalted
' hashes are vulnerable to precomputed/rainbow-table attacks. If a real
' Users table is introduced, prefer storing a random per-user salt alongside
' the hash and hashing (salt & password) - that is a schema change, so it is
' intentionally not done here.
'=======================================================================================================================

Function HashPassword(password)
Dim shell, command, execObj, outputLine, result

' Create Shell Object
Set shell = CreateObject("WScript.Shell")

' Construct PowerShell Command

command = "cmd /c powershell -ExecutionPolicy Bypass -NoLogo -NoProfile -File """ & Server.MapPath(".") & "..\Core\hash_sha256.ps1"" -password " & password
' Execute Command
Set execObj = shell.Exec(command)
' Read Output
Do While Not execObj.StdOut.AtEndOfStream
outputLine = Trim(execObj.StdOut.ReadAll())
If outputLine <> "" Then
result = outputLine ' Capture the hash
End If
Private Function Sha256_U32(n)
If n < 0 Then
Sha256_U32 = n + 4294967296.0
Else
Sha256_U32 = CDbl(n)
End If
End Function

Private Function Sha256_S32(d)
Do While d >= 4294967296.0
d = d - 4294967296.0
Loop
Do While d < 0
d = d + 4294967296.0
Loop
If d >= 2147483648.0 Then
Sha256_S32 = CLng(d - 4294967296.0)
Else
Sha256_S32 = CLng(d)
End If
End Function

Private Function Sha256_Mod(x, y)
Sha256_Mod = x - Int(x / y) * y
End Function

Private Function Sha256_Add32(a, b)
Sha256_Add32 = Sha256_S32(Sha256_U32(a) + Sha256_U32(b))
End Function

Private Function Sha256_Add32_5(a, b, c, d, e)
Sha256_Add32_5 = Sha256_S32(Sha256_U32(a) + Sha256_U32(b) + Sha256_U32(c) + Sha256_U32(d) + Sha256_U32(e))
End Function

Private Function Sha256_RotR32(x, n)
Dim u, partA, keepBits, partB
If n = 0 Then
Sha256_RotR32 = x
Exit Function
End If
u = Sha256_U32(x)
partA = Int(u / (2 ^ n))
keepBits = Sha256_Mod(u, 2 ^ n)
partB = keepBits * (2 ^ (32 - n))
Sha256_RotR32 = Sha256_S32(partA + partB)
End Function

Private Function Sha256_ShR32(x, n)
Dim u
u = Sha256_U32(x)
Sha256_ShR32 = Sha256_S32(Int(u / (2 ^ n)))
End Function

Private Function Sha256_BytesToWord(b0, b1, b2, b3)
Sha256_BytesToWord = Sha256_S32(b0 * 16777216.0 + b1 * 65536.0 + b2 * 256.0 + b3)
End Function

Private Function Sha256_HexWord(x)
Dim u, h
u = Sha256_U32(x)
h = ""
Do While u > 0
h = Mid("0123456789abcdef", Sha256_Mod(u, 16) + 1, 1) & h
u = Int(u / 16)
Loop
Sha256_HexWord = Right("00000000" & h, 8)
End Function

' Converts a VBScript string (UTF-16) to a UTF-8 byte array (Long values 0-255).
' Pure VBScript - avoids ADODB.Stream, which returns a Byte SafeArray that is
' not reliably indexable from VBScript on every 32/64-bit IIS configuration.
Private Function Sha256_StringToUtf8Bytes(s)
Dim arr(), count, i, code, lenS
lenS = Len(s)
If lenS = 0 Then
ReDim arr(-1)
Sha256_StringToUtf8Bytes = arr
Exit Function
End If
ReDim arr(lenS * 3)
count = -1
For i = 1 To lenS
code = AscW(Mid(s, i, 1))
If code < 0 Then code = code + 65536
If code <= 127 Then
count = count + 1 : arr(count) = code
ElseIf code <= 2047 Then
count = count + 1 : arr(count) = 192 Or Int(code / 64)
count = count + 1 : arr(count) = 128 Or (code And 63)
Else
count = count + 1 : arr(count) = 224 Or Int(code / 4096)
count = count + 1 : arr(count) = 128 Or (Int(code / 64) And 63)
count = count + 1 : arr(count) = 128 Or (code And 63)
End If
Next
ReDim Preserve arr(count)
Sha256_StringToUtf8Bytes = arr
End Function

' Cleanup
Set shell = Nothing
Set execObj = Nothing
' Returns the SHA-256 hash of inputString (UTF-8 encoded) as a 64-character
' lowercase hex string. Verified against NIST/RFC test vectors (empty string,
' "abc", the 56-byte two-block vector, and non-ASCII input) plus .NET's
' System.Security.Cryptography.SHA256 as a cross-check.
Function Sha256Hex(inputString)
Dim msg, msgLen, bitLen, i, t
Dim numBlocks, blockIdx, base
Dim hs(7)
Dim k(63)
Dim w(63)
Dim a, b, c, d, e, f, g, h
Dim s0, s1, ch, maj, temp1, temp2, bigS0, bigS1
Dim padded()
Dim padLen, totalLen, lenPos, hi32, lo32
Dim kHex, result

' Return the hash or error message
If result = "" Or Left(result, 5) = "ERROR" Then
HashPassword = result ' "ERROR: Hash not generated"
hs(0) = &h6a09e667 : hs(1) = &hbb67ae85 : hs(2) = &h3c6ef372 : hs(3) = &ha54ff53a
hs(4) = &h510e527f : hs(5) = &h9b05688c : hs(6) = &h1f83d9ab : hs(7) = &h5be0cd19

kHex = Array( _
"428a2f98","71374491","b5c0fbcf","e9b5dba5","3956c25b","59f111f1","923f82a4","ab1c5ed5", _
"d807aa98","12835b01","243185be","550c7dc3","72be5d74","80deb1fe","9bdc06a7","c19bf174", _
"e49b69c1","efbe4786","0fc19dc6","240ca1cc","2de92c6f","4a7484aa","5cb0a9dc","76f988da", _
"983e5152","a831c66d","b00327c8","bf597fc7","c6e00bf3","d5a79147","06ca6351","14292967", _
"27b70a85","2e1b2138","4d2c6dfc","53380d13","650a7354","766a0abb","81c2c92e","92722c85", _
"a2bfe8a1","a81a664b","c24b8b70","c76c51a3","d192e819","d6990624","f40e3585","106aa070", _
"19a4c116","1e376c08","2748774c","34b0bcb5","391c0cb3","4ed8aa4a","5b9cca4f","682e6ff3", _
"748f82ee","78a5636f","84c87814","8cc70208","90befffa","a4506ceb","bef9a3f7","c67178f2")
For i = 0 To 63
k(i) = Sha256_S32(CDbl("&h" & kHex(i)))
Next

msg = Sha256_StringToUtf8Bytes(inputString)
If UBound(msg) < LBound(msg) Then
msgLen = 0
Else
HashPassword = result
msgLen = UBound(msg) - LBound(msg) + 1
End If
bitLen = msgLen * 8.0

' Padding: msg + 0x80 + zero bytes so length % 64 = 56, then an 8-byte
' big-endian bit length.
padLen = 56 - Sha256_Mod(msgLen + 1, 64)
If padLen < 0 Then padLen = padLen + 64
totalLen = msgLen + 1 + padLen + 8

ReDim padded(totalLen - 1)
For i = 0 To msgLen - 1
padded(i) = msg(i)
Next
padded(msgLen) = 128
For i = msgLen + 1 To msgLen + padLen
padded(i) = 0
Next

lenPos = msgLen + 1 + padLen
hi32 = Int(bitLen / 4294967296.0)
lo32 = bitLen - hi32 * 4294967296.0
padded(lenPos + 0) = Sha256_Mod(Int(hi32 / 16777216), 256)
padded(lenPos + 1) = Sha256_Mod(Int(hi32 / 65536), 256)
padded(lenPos + 2) = Sha256_Mod(Int(hi32 / 256), 256)
padded(lenPos + 3) = Sha256_Mod(hi32, 256)
padded(lenPos + 4) = Int(lo32 / 16777216)
padded(lenPos + 5) = Sha256_Mod(Int(lo32 / 65536), 256)
padded(lenPos + 6) = Sha256_Mod(Int(lo32 / 256), 256)
padded(lenPos + 7) = Sha256_Mod(lo32, 256)

numBlocks = totalLen / 64

For blockIdx = 0 To numBlocks - 1
base = blockIdx * 64
For t = 0 To 15
w(t) = Sha256_BytesToWord(padded(base + t*4), padded(base + t*4 + 1), padded(base + t*4 + 2), padded(base + t*4 + 3))
Next
For t = 16 To 63
s0 = Sha256_RotR32(w(t-15), 7) Xor Sha256_RotR32(w(t-15), 18) Xor Sha256_ShR32(w(t-15), 3)
s1 = Sha256_RotR32(w(t-2), 17) Xor Sha256_RotR32(w(t-2), 19) Xor Sha256_ShR32(w(t-2), 10)
w(t) = Sha256_Add32_5(w(t-16), s0, w(t-7), s1, 0)
Next

a = hs(0) : b = hs(1) : c = hs(2) : d = hs(3)
e = hs(4) : f = hs(5) : g = hs(6) : h = hs(7)

For t = 0 To 63
bigS1 = Sha256_RotR32(e,6) Xor Sha256_RotR32(e,11) Xor Sha256_RotR32(e,25)
ch = (e And f) Xor ((Not e) And g)
temp1 = Sha256_Add32_5(h, bigS1, ch, k(t), w(t))
bigS0 = Sha256_RotR32(a,2) Xor Sha256_RotR32(a,13) Xor Sha256_RotR32(a,22)
maj = (a And b) Xor (a And c) Xor (b And c)
temp2 = Sha256_Add32(bigS0, maj)

h = g
g = f
f = e
e = Sha256_Add32(d, temp1)
d = c
c = b
b = a
a = Sha256_Add32(temp1, temp2)
Next

hs(0) = Sha256_Add32(hs(0), a) : hs(1) = Sha256_Add32(hs(1), b) : hs(2) = Sha256_Add32(hs(2), c) : hs(3) = Sha256_Add32(hs(3), d)
hs(4) = Sha256_Add32(hs(4), e) : hs(5) = Sha256_Add32(hs(5), f) : hs(6) = Sha256_Add32(hs(6), g) : hs(7) = Sha256_Add32(hs(7), h)
Next

result = ""
For i = 0 To 7
result = result & Sha256_HexWord(hs(i))
Next
Sha256Hex = result
End Function

Function HashPassword(password)
HashPassword = Sha256Hex(password)
End Function

'=======================================================================================================================
' CheckPassword
'
' The original implementation called CreateRepository(conn, "Users", "UserId")
' and Array("UserName", user) - "conn" and "CreateRepository" are not defined
' anywhere in this framework (no global "conn", no CreateRepository factory),
' and it searched using the not-yet-assigned "user" variable instead of the
' "username" parameter, so this function has never been callable. Nothing in
' this codebase calls it yet, and there is no Users table/migration either -
' this remains example/scaffold code. Rewritten here to use the framework's
' actual data-access primitive (DAL().Query with a parameterized command) and
' the correct lookup value, so it will work once a Users table exists with
' UserName and PasswordHash columns.
'=======================================================================================================================
Function CheckPassword(username, password)
Dim user,UsersRepository
Set UsersRepository = CreateRepository(conn, "Users", "UserId")
' Find User
Set User = UsersRepository.Find(Array("UserName", user), Empty)
If user Is Nothing Then Exit Function ' Implicitly returns False
Dim rs
Set rs = DAL().Query("SELECT PasswordHash FROM Users WHERE UserName = ?", Array(username))

If rs.EOF Then
CheckPassword = False
Else
CheckPassword = (HashPassword(password) = rs("PasswordHash"))
End If

' Compare Hashed Password
CheckPassword = (HashPassword(password) = user.PasswordHash)
rs.Close
Set rs = Nothing
End Function
%>
%>

+ 1
- 0
core/lib.helpers.asp Прегледај датотеку

@@ -1,4 +1,5 @@
<%
Dim protocol
'protocol = IIf(LCase(Request.ServerVariables("HTTPS")) = "1", "https", "http")
protocol = "https"
Dim timeZones


+ 1
- 0
core/lib.json.asp Прегледај датотеку

@@ -260,6 +260,7 @@ Class aspJSON
End Function

Private Function aj_ReadNumericValue(ByVal val)
Dim numdecimals
If Instr(val, ".") > 0 Then
numdecimals = Len(val) - Instr(val, ".")
val = Clng(Replace(val, ".", ""))


+ 0
- 166
core/mvc.asp Прегледај датотеку

@@ -1,166 +0,0 @@
<!--#include file="../app/Controllers/autoload_controllers.asp" -->
<%
' Set cache expiration from configuration
Dim cacheYear : cacheYear = GetAppSetting("CacheExpirationYear")
If cacheYear = "nothing" Then cacheYear = "2030"
Response.ExpiresAbsolute = "01/01/" & cacheYear
Response.AddHeader "pragma", "no-cache"
Response.AddHeader "cache-control", "private, no-cache, must-revalidate"
'=======================================================================================================================
' MVC Dispatcher
'=======================================================================================================================
Class MVC_Dispatcher_Class
dim CurrentController

Public Property Get ControllerName
ControllerName = CurrentController
end Property

'---------------------------------------------------------------------------------------------------------------------
' Convenience method to resolve route and dispatch in one call
' method: HTTP method (GET, POST, etc.)
' path: Request path (already cleaned of query params)
'---------------------------------------------------------------------------------------------------------------------
Public Sub DispatchRequest(method, path)
Dim routeArray
routeArray = router.Resolve(method, path)
Dispatch routeArray
End Sub

'---------------------------------------------------------------------------------------------------------------------
' Main dispatch method - executes a resolved route
' RouteArray: Array(controller, action, params) from router.Resolve()
'---------------------------------------------------------------------------------------------------------------------
Public Sub Dispatch(RouteArray)
On Error Resume Next
Dim controllerName, actionName, hasParams, paramsArray
controllerName = RouteArray(0)
actionName = RouteArray(1)

' Security: Validate controller and action names
If Not ControllerRegistry.IsValidControllerFormat(controllerName) Then
Response.Write "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; border-radius:4px;'>"
Response.Write "<strong>Security Error:</strong> Invalid controller name format."
Response.Write "</div>"
Exit Sub
End If

If Not ControllerRegistry.IsValidActionFormat(actionName) Then
Response.Write "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; border-radius:4px;'>"
Response.Write "<strong>Security Error:</strong> Invalid action name format."
Response.Write "</div>"
Exit Sub
End If

' Security: Check controller whitelist
If Not ControllerRegistry.IsValidController(controllerName) Then
Response.Write "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; border-radius:4px;'>"
Response.Write "<strong>Security Error:</strong> Controller '" & Server.HTMLEncode(controllerName) & "' is not registered."
Response.Write "</div>"
Exit Sub
End If

' Initialize current controller
Dim controllerAssignment : controllerAssignment = "Set CurrentController = " & controllerName & "()"
Execute controllerAssignment

' Check if layout should be used
hasParams = (UBound(RouteArray) >= 2)
If eval(controllerName & ".useLayout") Then
%> <!-- #include file="../app/views/Shared/Header.asp" --> <%
End If

' Prepare parameters
If hasParams Then
paramsArray = SurroundStringInArray(RouteArray(2))
Else
paramsArray = Empty
End If

' Execute controller action
ExecuteControllerAction controllerName, actionName, paramsArray

' Include footer if layout is used
If eval(controllerName & ".useLayout") Then
%> <!-- #include file="../app/views/Shared/Footer.asp" --> <%
End If
On Error GoTo 0
End Sub

' Helper method to execute controller actions (eliminates code duplication)
Private Sub ExecuteControllerAction(controllerName, actionName, paramsArray)
On Error Resume Next
Dim callString

' Build the call string based on whether we have parameters
If Not IsEmpty(paramsArray) And IsArray(paramsArray) And UBound(paramsArray) >= 0 Then
callString = "Call " & controllerName & "." & actionName & "(" & Join(paramsArray, ",") & ")"
Else
callString = "Call " & controllerName & "." & actionName & "()"
End If

' Execute the action
Execute callString

' Handle errors
If Err.Number <> 0 Then
HandleDispatchError actionName, Err.Description, Err.Number
Err.Clear
End If
On Error GoTo 0
End Sub

' Centralized error handling for dispatch errors
Private Sub HandleDispatchError(actionName, errorDesc, errorNum)
Dim isDevelopment
isDevelopment = (LCase(GetAppSetting("Environment")) = "development")

If isDevelopment Then
Response.Write "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; border-radius:4px;'>"
Response.Write "<strong>Controller Action Error</strong><br>"
Response.Write "Action: <code>" & Server.HTMLEncode(actionName) & "</code><br>"
Response.Write "Error: " & Server.HTMLEncode(errorDesc) & "<br>"
Response.Write "Error Number: " & errorNum
Response.Write "</div>"
Else
Response.Write "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; border-radius:4px;'>"
Response.Write "<strong>An error occurred</strong><br>"
Response.Write "Please contact the system administrator if the problem persists."
Response.Write "</div>"
End If
End Sub
Public Sub RequirePost
If Request.Form.Count = 0 Then MVC.RedirectToExt "NotValid","",empty:End If

End Sub

' Shortcut for RedirectToActionExt that does not require passing a parameters argument.
Public Sub RedirectToAction(ByVal action_name)
RedirectToActionExt action_name, empty
End Sub

Public Sub RedirectTo(controller_name, action_name)
RedirectToExt controller_name, action_name, empty
End Sub
' Redirects the browser to the specified action on the specified controller with the specified querystring parameters.
' params is a KVArray of querystring parameters.
Public Sub RedirectToExt(controller_name, action_name, params)
Response.Redirect Routes.UrlTo(controller_name, action_name, params)
End Sub

Public Sub RedirectToActionExt(ByVal action_name, ByVal params)
RedirectToExt ControllerName, action_name, params
End Sub

End Class
dim MVC_Dispatcher_Class__Singleton
Function MVC()
if IsEmpty(MVC_Dispatcher_Class__Singleton) then
set MVC_Dispatcher_Class__Singleton = new MVC_Dispatcher_Class
end if
set MVC = MVC_Dispatcher_Class__Singleton
End Function
%>

+ 320
- 0
core/mvc.wsc Прегледај датотеку

@@ -0,0 +1,320 @@
<?xml version="1.0"?>
<!-- MVCDispatcher.wsc -->
<component>

<!-- COM registration -->
<registration
description = "Classic ASP MVC Dispatcher Component"
progid = "App.MVCDispatcher"
version = "1.0"
classid = "{C3D4E5F6-7A8B-49C0-8D1E-2F3A4B5C6D7E}" />

<!-- Public interface -->
<public>

<property name="Router">
<put internalName="PutRouter"/>
</property>
<property name="ControllerRegistry">
<put internalName="PutControllerRegistry"/>
</property>
<property name="ControllerFactory">
<put internalName="PutControllerFactory"/>
</property>
<property name="Routes">
<put internalName="PutRoutes"/>
</property>

<property name="ControllerName">
<get internalName="GetControllerName"/>
</property>
<property name="CurrentController">
<get internalName="GetCurrentController"/>
</property>
<property name="UseLayout">
<get internalName="GetUseLayout"/>
</property>
<property name="LastError">
<get internalName="GetLastError"/>
</property>

<method name="Resolve"/>
<method name="ExecuteAction"/>
<method name="DispatchRequest"/>
<method name="RequirePost"/>
<method name="RedirectToAction"/>
<method name="RedirectTo"/>
<method name="RedirectToExt"/>
<method name="RedirectToActionExt"/>

</public>

<!-- Give the component ASP intrinsic objects (Request, Response, Server ...) -->
<implements type="ASP"/>

<!-- Implementation -->
<script language="VBScript">
<![CDATA[
Option Explicit

'------------------------------------------------------------
' Injected dependencies
'
' A WSC runs in its own isolated script engine, so it cannot see the
' including ASP page's globals (GetAppSetting, ControllerRegistry(),
' Routes(), router, the per-controller singleton functions, etc.) the
' way mvc.asp used to. Per AGENTS.md section 69/70 ("Dependencies Must
' Be Injected", "Avoid Hidden Dependencies"), everything this component
' needs from the outside is passed in explicitly instead.
'
' Properties here are wired to plain functions (PutX/GetX) via
' internalName in the <public> section above, rather than VBScript
' "Property Get/Let/Set" syntax - that syntax is only legal inside an
' explicit Class...End Class block (a general VBScript rule, not
' specific to WSC), and this component's members are declared directly
' in the script rather than inside a Class, matching router.wsc's
' existing convention in this project.
'------------------------------------------------------------
Private m_router
Private m_controllerRegistry
Private m_controllerFactory
Private m_routes

Private m_currentController
Private m_controllerName ' "Controller" suffix stripped, e.g. "Home"
Private m_actionName
Private m_params
Private m_lastError

Sub PutRouter(value)
Set m_router = value
End Sub

Sub PutControllerRegistry(value)
Set m_controllerRegistry = value
End Sub

Sub PutControllerFactory(value)
Set m_controllerFactory = value
End Sub

Sub PutRoutes(value)
Set m_routes = value
End Sub

'------------------------------------------------------------
' Read-only state exposed to callers/views
'------------------------------------------------------------

' Name of the controller most recently resolved by Resolve(), with the
' "Controller" suffix stripped (e.g. "Home", not "HomeController").
Function GetControllerName()
GetControllerName = m_controllerName
End Function

' The controller object itself, e.g. so a layout can read CurrentController.Title.
Function GetCurrentController()
Set GetCurrentController = m_currentController
End Function

Function GetUseLayout()
If IsObject(m_currentController) Then
GetUseLayout = m_currentController.useLayout
Else
GetUseLayout = False
End If
End Function

' Nothing when the last Resolve()/ExecuteAction() succeeded. Otherwise a
' Dictionary with "Type" ("Security" or "Action") plus either "Message"
' (Security) or "ActionName"/"Number"/"Description" (Action). Rendering
' this is the caller's job - per AGENTS.md's "Controllers Coordinate;
' They Do Not Render", this component never calls Response.Write.
Function GetLastError()
Set GetLastError = m_lastError
End Function

'------------------------------------------------------------
' Resolve(method, path) -> Boolean
'
' Resolves the route, validates the controller/action name format and
' whitelist, and creates the controller via the injected ControllerFactory.
' Returns False and sets LastError (Type = "Security") if any check fails.
' Must be called before ExecuteAction().
'------------------------------------------------------------
Public Function Resolve(method, path)
Dim routeArray, controllerNameRaw, actionNameRaw

Set m_lastError = Nothing
Set m_currentController = Nothing
m_controllerName = ""
m_actionName = ""
m_params = Empty

routeArray = m_router.Resolve(method, path)
controllerNameRaw = routeArray(0)
actionNameRaw = routeArray(1)

If Not m_controllerRegistry.IsValidControllerFormat(controllerNameRaw) Then
SetSecurityError "Invalid controller name format."
Resolve = False
Exit Function
End If

If Not m_controllerRegistry.IsValidActionFormat(actionNameRaw) Then
SetSecurityError "Invalid action name format."
Resolve = False
Exit Function
End If

If Not m_controllerRegistry.IsValidController(controllerNameRaw) Then
SetSecurityError "Controller '" & controllerNameRaw & "' is not registered."
Resolve = False
Exit Function
End If

Set m_currentController = m_controllerFactory.Create(controllerNameRaw)
m_controllerName = StripControllerSuffix(controllerNameRaw)
m_actionName = actionNameRaw

If UBound(routeArray) >= 2 Then
m_params = SurroundParamsInArray(routeArray(2))
End If

Resolve = True
End Function

'------------------------------------------------------------
' ExecuteAction()
'
' Invokes the resolved action on the resolved controller. Must follow a
' successful Resolve(). Populates LastError (Type = "Action") on failure
' instead of raising or writing HTML, so the caller decides how to show it.
'
' Named ExecuteAction rather than Execute because VBScript's Execute
' STATEMENT (used below to invoke the action by name) is shadowed by any
' Sub/Function of the same name in scope - a Sub literally named "Execute"
' cannot call the Execute statement from inside itself.
'
' The action name is still dispatched via a scoped Execute, since
' controllers do not (yet) implement the Invoke() reflection convention
' from AGENTS.md section 69 - that is a larger, separate change to every
' controller's contract. What this fixes is the CONTROLLER resolution:
' the original mvc.asp built "Set CurrentController = " & controllerName
' & "()" from a route-supplied string and Executed it, resolving an
' arbitrary global name at dispatch time. Here the controller always comes
' from the injected ControllerFactory (whitelisted, explicitly registered
' ahead of time), and this Execute only ever calls a method by name on
' that already-known object.
'------------------------------------------------------------
Public Sub ExecuteAction()
Dim callString, errInfo

If Not IsObject(m_currentController) Then
Err.Raise vbObjectError + 1200, "MVCDispatcher.ExecuteAction", "ExecuteAction called before a successful Resolve."
End If

If IsArray(m_params) Then
If UBound(m_params) >= 0 Then
callString = "Call m_currentController." & m_actionName & "(" & Join(m_params, ",") & ")"
Else
callString = "Call m_currentController." & m_actionName & "()"
End If
Else
callString = "Call m_currentController." & m_actionName & "()"
End If

On Error Resume Next
Execute callString

If Err.Number <> 0 Then
Set errInfo = Server.CreateObject("Scripting.Dictionary")
errInfo.Add "Type", "Action"
errInfo.Add "ActionName", m_actionName
errInfo.Add "Number", Err.Number
errInfo.Add "Description", Err.Description
Set m_lastError = errInfo
Err.Clear
End If
On Error Goto 0
End Sub

'------------------------------------------------------------
' DispatchRequest(method, path)
'
' Convenience wrapper for callers that do not need layout wrapping or
' custom error rendering (e.g. tests): Resolve then ExecuteAction, silently
' doing nothing further on a Resolve failure. Callers that need to wrap
' output in a layout or render errors (the real app's entry point) should
' call Resolve/UseLayout/ExecuteAction/LastError directly instead - see
' public/Default.asp.
'------------------------------------------------------------
Public Sub DispatchRequest(method, path)
If Resolve(method, path) Then
ExecuteAction()
End If
End Sub

'------------------------------------------------------------
Public Sub RequirePost()
If Request.Form.Count = 0 Then
RedirectToExt "NotValid", "", Empty
End If
End Sub

' Shortcut for RedirectToActionExt that does not require passing a parameters argument.
Public Sub RedirectToAction(action_name)
RedirectToActionExt action_name, Empty
End Sub

Public Sub RedirectTo(controller_name, action_name)
RedirectToExt controller_name, action_name, Empty
End Sub

' Redirects the browser to the specified action on the specified controller with the
' specified querystring parameters. params is a KVArray of querystring parameters.
Public Sub RedirectToExt(controller_name, action_name, params)
Response.Redirect m_routes.UrlTo(controller_name, action_name, params)
End Sub

Public Sub RedirectToActionExt(action_name, params)
RedirectToExt m_controllerName, action_name, params
End Sub

'------------------------------------------------------------
' Private helpers
'------------------------------------------------------------
Private Sub SetSecurityError(message)
Dim errInfo
Set errInfo = Server.CreateObject("Scripting.Dictionary")
errInfo.Add "Type", "Security"
errInfo.Add "Message", message
Set m_lastError = errInfo
End Sub

Private Function StripControllerSuffix(name)
Const suffix = "Controller"
If Len(name) > Len(suffix) And LCase(Right(name, Len(suffix))) = LCase(suffix) Then
StripControllerSuffix = Left(name, Len(name) - Len(suffix))
Else
StripControllerSuffix = name
End If
End Function

' Wraps string route params in quotes so they can be spliced into the
' dynamically-built Execute call string in ExecuteAction() above. Equivalent
' to the app-side SurroundStringInArray() helper, duplicated here (rather
' than called) because this WSC cannot see that global function.
Private Function SurroundParamsInArray(arr)
Dim i, result
result = arr
For i = LBound(result) To UBound(result)
If TypeName(result(i)) = "String" Then
result(i) = """" & result(i) & """"
End If
Next
SurroundParamsInArray = result
End Function
]]>
</script>
</component>

+ 0
- 1543
docs/AGENTS.md
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 0
- 994
docs/CLAUDE.md Прегледај датотеку

@@ -1,994 +0,0 @@
# CLAUDE.md

## Purpose

This file defines how Claude and Claude-compatible coding agents should operate in this repository.

Read `AGENTS.md` first.

`AGENTS.md` is the primary development policy.

This file defines agent workflow, reasoning discipline, project learning, and self-improvement behavior.

---

# 1. Startup Procedure

At the beginning of a development task:

1. Read `AGENTS.md`.
2. Read this `CLAUDE.md`.
3. Inspect relevant files before editing.
4. Search the repository for similar implementations.
5. Identify existing helpers and abstractions.
6. Determine the affected architectural layers.
7. Identify security-sensitive boundaries.
8. Identify tests relevant to the change.

Do not immediately generate a new abstraction before understanding the existing project.

---

# 2. Treat VBScript as a Language Requiring Agent-Level Static Analysis

VBScript lacks many safeguards found in modern compiled languages.

Therefore, while working, actively inspect for:

```text
undeclared variables
misspelled identifiers
missing Set
wrong argument counts
implicit Variant conversions
Null propagation
Empty values
Nothing references
unexpected ByRef mutation
invalid function return paths
unsafe SQL
unsafe HTML output
broad error suppression
resource leaks
global-state coupling
```

Act as a compiler substitute.

---

# 3. Think in Boundaries

Before changing code, determine which layer owns the behavior.

Preferred direction:

```text
Request
Controller
Service
Repository
Database
```

Views should receive prepared data.

Do not bypass layers for convenience.

---

# 4. Avoid Architectural Drift

When adding functionality, prefer the project's existing good pattern.

Do not create:

```text
CustomerManager
CustomerHandler
CustomerEngine
CustomerProcessor
CustomerHelper
```

if `CustomerService` already represents the business layer.

Reuse terminology consistently.

---

# 5. Search Before Creating

Before creating:

- a helper
- service
- repository
- validator
- utility
- model
- WSC component
- framework function

search for an existing implementation.

Prefer extending an existing appropriate abstraction over creating duplicates.

---

# 6. Make Small Changes

Prefer the smallest coherent change that solves the problem.

Avoid unrelated cleanup unless necessary.

If a task exposes a larger architectural problem:

1. solve the requested problem safely
2. document the larger concern
3. improve the architecture when justified

Do not turn every bug fix into a rewrite.

---

# 7. Never Hide Errors to Make Code Appear Functional

Do not use broad:

```vbscript
On Error Resume Next
```

to make failures disappear.

Errors should be:

```text
prevented
handled
logged
or propagated
```

Never silently swallowed.

---

# 8. Security Review Is Mandatory

For each change involving input, output, authentication, database access, file handling, or state changes, inspect for:

```text
SQL injection
XSS
CSRF
authorization bypass
open redirects
path traversal
unsafe file upload
sensitive logging
session misuse
```

Security checks are not optional even if the user did not explicitly request them.

---

# 9. SQL Generation

Always prefer parameterized ADODB commands.

If existing code concatenates SQL, do not copy the unsafe pattern into new code.

When touching the unsafe section directly, improve it if doing so is reasonably scoped and compatibility can be preserved.

---

# 10. HTML Generation

Dynamic text should be HTML encoded by default.

Do not output raw Request or database values into HTML.

When raw HTML is intentionally supported, make that trust boundary explicit.

---

# 11. Validate at the Boundary

Convert raw external values into trusted application values early.

Think:

```text
Raw Request value
Trim / normalize
Required check
Format validation
Type conversion
Business validation
Use
```

Do not allow raw Request values to flow deeply through the system.

---

# 12. Prefer Explicit Code

In VBScript, explicit code is usually safer than clever code.

Prefer:

```vbscript
Dim customerId
customerId = CLng(value)
```

over relying on automatic coercion.

Prefer clear control flow over compressed expressions.

---

# 13. Functions and Procedures

Keep procedures focused.

If a function performs several unrelated operations, extract meaningful collaborators.

Prefer names that describe intent.

Bad:

```text
ProcessData
HandleStuff
DoWork
RunThing
```

Better:

```text
ValidateCustomer
CalculateOrderTotal
LoadCustomerById
SaveAppointment
```

---

# 14. ByVal and ByRef

Remember:

VBScript defaults parameters to `ByRef`.

When mutation is not intended, prefer:

```vbscript
ByVal
```

Use `ByRef` only when caller mutation is intentional.

Review changed method signatures for accidental ByRef behavior.

---

# 15. Object Assignment

Remember that object assignment requires `Set`.

Review every new or modified object assignment.

Example:

```vbscript
Set service = New CustomerService
```

not:

```vbscript
service = New CustomerService
```

---

# 16. Function Return Values

VBScript functions return values by assigning to their own name.

Review all paths.

Example:

```vbscript
Function IsAllowed(ByVal userId)

If userId <= 0 Then
IsAllowed = False
Exit Function
End If

IsAllowed = True

End Function
```

Do not accidentally leave an important return value as Empty.

---

# 17. COM and ADODB Cleanup

When creating:

- ADODB.Connection
- ADODB.Command
- ADODB.Recordset
- filesystem objects
- external COM components

understand ownership and cleanup behavior.

Explicitly close database resources when appropriate.

Set references to `Nothing` when ownership ends.

---

# 18. Avoid Global State

Prefer explicit dependencies.

Do not add Session or Application state merely because it is convenient.

Ask:

```text
Does this value truly need to survive across requests?
```

If not, keep it request-scoped.

---

# 19. Testing Strategy

When possible, isolate business logic from ASP globals.

Favor code that can be invoked with plain VBScript values or mock collaborators.

When fixing a defect, add a regression test where practical.

---

# 20. Comments

Comments should explain:

```text
why
constraints
non-obvious behavior
compatibility requirements
security rationale
```

Do not add comments that merely repeat the code.

Bad:

```vbscript
' Set customer id
customerId = 10
```

Useful:

```vbscript
' Legacy import files use 0 to represent an unknown customer.
If customerId = 0 Then
```

---

# 21. Preserve Valuable Legacy Behavior

ASP Classic projects may contain old but important behavior.

Do not assume unfamiliar code is wrong.

Before removing or replacing something:

1. search references
2. understand callers
3. identify side effects
4. inspect related documentation
5. preserve compatibility where required

---

# 22. Self-Improvement

This repository is designed to allow agents to improve their own operating instructions.

Self-improvement should make future work:

- safer
- more consistent
- easier to verify
- less repetitive
- more maintainable

Do not change instructions merely to suit the current implementation.

Improve the implementation when practical rather than weakening good rules.

---

# 23. Detect Learnable Events

A learnable event occurs when:

- a repeated bug appears
- a review catches the same mistake repeatedly
- a new reusable pattern proves successful
- a project architecture decision becomes permanent
- a new security requirement becomes necessary
- a repeated workflow can be standardized
- a user establishes a lasting convention
- an existing instruction becomes inaccurate

When a learnable event occurs, determine whether project documentation should change.

---

# 24. Where Learning Should Go

Use the smallest appropriate scope.

Update:

```text
AGENTS.md
```

for project-wide durable policies.

Update:

```text
CLAUDE.md
```

for agent workflow and Claude-specific operating behavior.

Update:

```text
/docs/*
```

for architectural or project documentation.

Update:

```text
/skills/*
```

for reusable specialized procedures.

Update source-code comments only for code-local constraints.

Do not place every lesson into AGENTS.md.

---

# 25. Self-Update Decision

Before modifying an instruction file, ask:

```text
Is this lesson likely to matter again?
```

If no, do not persist it.

Then ask:

```text
Is this project-wide?
```

If yes, consider AGENTS.md.

Otherwise use a specialized document.

---

# 26. Never Self-Weaken Core Safety Rules

Claude must not autonomously remove or weaken requirements involving:

```text
Option Explicit
parameterized SQL
server-side authorization
input validation
output encoding
safe error handling
resource cleanup
secret protection
```

If an unusual project requirement appears to conflict with one of these rules, preserve safety and document the conflict.

---

# 27. Avoid Instruction Inflation

Do not add a new rule for every isolated mistake.

Prefer:

```text
general principle
+
reusable example
```

rather than hundreds of hyper-specific rules.

A rule should prevent a category of mistakes.

---

# 28. Improve Through Abstractions Too

Not every lesson belongs in documentation.

If the same safety requirement appears repeatedly, consider encoding it into the framework.

Examples:

```text
Safe HTML helper
Parameterized database wrapper
Request conversion helper
Validation library
Authorization service
Logging wrapper
Repository base conventions
```

The best rule is sometimes one the architecture makes difficult to violate.

---

# 29. Skill Creation

Claude may create a new skill when the task is:

- specialized
- recurring
- procedural
- reusable across multiple features

Possible skills:

```text
/skills/asp-classic-controller.md
/skills/adodb-repository.md
/skills/sql-parameterization.md
/skills/security-review.md
/skills/vbscript-static-review.md
/skills/testing.md
```

Do not create a skill for a one-time task.

---

# 30. Suggested Skill Format

Each skill should contain:

```text
# Skill Name

## Purpose

## Use When

## Inputs

## Procedure

## Required Rules

## Validation Checklist

## Common Mistakes

## Output

## Improvement Notes
```

---

# 31. Agent Review Loop

For meaningful tasks, use this cycle:

```text
Understand
Inspect
Design
Implement
Review
Test
Learn
```

The `Learn` step asks whether the repository's instructions or abstractions should improve.

---

# 32. Design Before Implementation

For significant changes, identify:

```text
affected layers
new components
existing components
data flow
validation boundaries
security boundaries
failure behavior
testing strategy
```

before writing substantial code.

For small tasks, keep this lightweight.

---

# 33. Avoid Premature Framework Expansion

Do not add framework components simply because another language would have them.

ASP Classic should remain simple.

Only introduce an abstraction when it solves a real recurring problem.

---

# 34. Prefer Composition

When extending behavior, first consider:

```text
small object
+
small collaborator
```

instead of large pseudo-inheritance systems.

VBScript is better suited to simple composition.

---

# 35. Stable Public Contracts

Treat established routes, APIs, class methods, and database contracts carefully.

When changing them:

1. search callers
2. assess compatibility
3. update all consumers
4. document breaking changes

Avoid accidental interface changes.

---

# 36. Database Changes

For schema changes, consider:

```text
backward compatibility
migration order
existing data
Null behavior
indexes
constraints
application deployment timing
rollback implications
```

Do not modify schema casually.

---

# 37. Performance Review

When working with database code, look for:

```text
queries inside loops
SELECT *
unnecessary recordset traversal
missing filters
repeated connection creation
large Session values
unbounded result sets
unnecessary COM calls
```

Prefer database-side filtering and set operations.

---

# 38. Request Lifecycle Awareness

Remember that ASP Classic operates per request.

Avoid assumptions that local variables persist between requests.

Use Session or persistent storage only when the application truly requires cross-request state.

---

# 39. Application Lifecycle Awareness

`Application` state is global to the ASP application.

Do not store user-specific data there.

Be mindful of concurrent requests.

---

# 40. Avoid Hidden Dependencies

A component should not unexpectedly require:

```text
Request
Response
Session
Application
global connection object
global logger
global configuration
```

unless that dependency is part of the documented architecture.

Prefer passing dependencies explicitly.

---

# 41. Compatibility First During Refactoring

When modernizing legacy code:

```text
First preserve behavior.
Then improve structure.
```

Avoid combining major behavior changes with major refactors unless necessary.

---

# 42. Repository Review After Significant Changes

After a meaningful feature or refactor, search for:

- outdated documentation
- duplicate helpers
- now-dead code
- inconsistent naming
- old patterns replaced by the new pattern
- opportunities to update an existing skill

Keep the repository internally consistent.

---

# 43. Learn From User Corrections

If the user corrects Claude on a durable project convention, treat that as a possible learnable event.

Examples:

```text
"We always use this router."
"Repositories never return recordsets."
"All AJAX endpoints use this response format."
"Use WSC components for controllers."
```

If the instruction is durable, update the appropriate project guidance.

Do not persist one-time preferences as permanent architecture rules.

---

# 44. Change Documentation Conservatively

When modifying AGENTS.md, CLAUDE.md, or skills:

- preserve useful existing rules
- remove duplicates
- clarify contradictions
- avoid unnecessary rewrites
- add an update-history entry where appropriate

Instruction files should become clearer over time, not merely longer.

---

# 45. Conflict Detection

If project documentation contradicts itself:

1. identify the conflict
2. use higher-priority instructions
3. resolve the contradiction where safe
4. update documentation so future agents do not face the same conflict

Do not silently choose different interpretations from task to task.

---

# 46. Self-Improvement Is Not Autonomous Product Redesign

Claude may improve:

```text
guidelines
skills
documentation
developer workflow
internal abstractions
```

when justified.

Claude must not independently redefine:

```text
business requirements
user-visible behavior
pricing
authorization policy
data retention policy
major architecture goals
```

without task requirements supporting the change.

---

# 47. Final Review Mode

Before completing a task, temporarily switch from implementation thinking to adversarial review.

Ask:

```text
How could this fail?

What would VBScript fail to warn me about?

What input breaks this?

What happens with Null?

What happens with Empty?

What happens with Nothing?

Did I forget Set?

Could ByRef change the caller?

Could SQL injection occur?

Could XSS occur?

Could authorization be bypassed?

Could an error be swallowed?

Could a recordset remain open?

Did I accidentally duplicate existing functionality?
```

Fix issues found before finishing.

---

# 48. Final Summary

When reporting completed coding work, mention material items such as:

```text
what changed
important architecture choices
tests performed
security implications
documentation or rule updates
remaining limitations
```

Do not produce long explanations for trivial changes.

---

# 49. Core Philosophy

Claude should help make ASP Classic behave like a disciplined modern development environment without destroying the language's simplicity.

The goal is not to turn VBScript into C#.

The goal is to provide the safeguards that VBScript lacks.

Think:

```text
VBScript
+
clear architecture
+
strict conventions
+
reusable framework components
+
agent-level static analysis
+
continuous learning
=
reliable ASP Classic development
```

---

# Update History

2026-09-02
- Initial Claude operating instructions created.
- Added VBScript static-review behavior.
- Added controlled self-improvement workflow.
- Added learnable-event detection and skill creation guidance.

+ 36
- 3
public/Default.asp Прегледај датотеку

@@ -1,3 +1,12 @@
<%
' Classic ASP splices #include files together as raw text before compiling,
' and Option Explicit is only legal once, as the literal first statement of
' the resulting page. Default.asp pulls in the entire application (core/*,
' app/controllers/*, app/views/*) via autoload_core.asp below, so this single
' declaration enforces explicit variable declaration across all of it. Do not
' add another Option Explicit to any included file - see AGENTS.md.
Option Explicit
%>
<!--#include file="..\core\autoload_core.asp" -->

<%
@@ -7,7 +16,31 @@
router.AddRoute "GET", "", "HomeController", "Index"
router.AddRoute "GET", "/404", "ErrorController", "NotFound"

' Dispatch the request (resolves route and executes controller action)
MVC.DispatchRequest Request.ServerVariables("REQUEST_METHOD"), _
TrimQueryParams(Request.ServerVariables("HTTP_X_ORIGINAL_URL"))
' Resolve the route/controller/action. MVC is a Windows Script Component
' and (per AGENTS.md's "Controllers Coordinate; They Do Not Render") never
' writes HTML itself - Resolve()/ExecuteAction() only ever report success
' or populate LastError, and rendering happens here. It also cannot use
' Server-Side Includes, so the layout wrapping that used to happen inside
' Dispatch() now happens here, guarded by MVC.UseLayout.
Dim requestResolved
requestResolved = MVC.Resolve(Request.ServerVariables("REQUEST_METHOD"), _
TrimQueryParams(Request.ServerVariables("HTTP_X_ORIGINAL_URL")))

If Not requestResolved Then
RenderMvcError MVC.LastError
Else
If MVC.UseLayout Then
%><!--#include file="../app/views/Shared/Header.asp" --><%
End If

MVC.ExecuteAction()

If Not (MVC.LastError Is Nothing) Then
RenderMvcError MVC.LastError
End If

If MVC.UseLayout Then
%><!--#include file="../app/views/Shared/Footer.asp" --><%
End If
End If
%>

+ 13
- 1
run_site.cmd Прегледај датотеку

@@ -1,4 +1,16 @@
@echo off
setlocal
set "ASPC_STARTER_ROOT=%~dp0"
"C:\Program Files\IIS Express\iisexpress.exe" /config:"%~dp0applicationhost.config"
set "IISEXPRESS=C:\Program Files\IIS Express\iisexpress.exe"

rem IIS Express only runs ONE site per process, even when applicationhost.config
rem defines several with serverAutoStart="true" (that attribute is honored by
rem full IIS/W3SVC, not by a single ad-hoc iisexpress.exe invocation - per
rem "iisexpress /?": "/config:config-file ... runs the first site in the
rem specified configuration file"). So each site gets its own process here.

echo Starting production site (http://localhost:8080/) ...
start "IIS Express - Production (8080)" "%IISEXPRESS%" /config:"%~dp0applicationhost.config" /site:"Development Web Site"

echo Starting tests site (http://localhost:8081/) ...
start "IIS Express - Tests (8081)" "%IISEXPRESS%" /config:"%~dp0applicationhost.config" /site:"Tests Web Site"

+ 1
- 1
tests/component/web.config Прегледај датотеку

@@ -6,7 +6,7 @@
<add key="CacheExpirationYear" value="2030" />
<add key="EnableCacheBusting" value="false" />
<add key="CacheBustParamName" value="v" />
<add key="ProductionAppBaseUrl" value="http://localhost:8081/" />
<add key="ProductionAppBaseUrl" value="http://localhost:8080/" />
</appSettings>

<system.webServer>


+ 101
- 4
tests/integration/TestMvcDispatch.asp Прегледај датотеку

@@ -1,6 +1,7 @@
<!-- #include file="../aspunit/Lib/ASPUnit.asp" -->
<!-- #include file="../bootstrap.asp" -->
<!-- #include file="../../core/mvc.asp" -->
<!-- #include file="../../core/lib.ControllerFactory.asp" -->
<!-- #include file="../../core/lib.Routes.asp" -->

<%
Class TestDispatchController_Class
@@ -31,12 +32,70 @@ Function TestDispatchController()
Set TestDispatchController = TestDispatchController_Class__Singleton
End Function

' Named distinctly from TestDispatchController so the "Controller" suffix
' stripping performed by MVC.ControllerName is unambiguous to assert on.
Class TitleTestController_Class
Private m_useLayout

Private Sub Class_Initialize()
m_useLayout = False
End Sub

Public Property Get useLayout
useLayout = m_useLayout
End Property

Public Property Let useLayout(value)
m_useLayout = value
End Property

Public Sub Smoke()
dispatchActionRan = True
End Sub
End Class

Dim TitleTestController_Class__Singleton
Function TitleTestController()
If IsEmpty(TitleTestController_Class__Singleton) Then
Set TitleTestController_Class__Singleton = New TitleTestController_Class
End If
Set TitleTestController = TitleTestController_Class__Singleton
End Function

' MVC is now a Windows Script Component (core/mvc.wsc) with its own isolated
' script engine, so - like router.wsc via EnsureTestRouter() below - it is
' instantiated once and has its dependencies (Router, ControllerRegistry,
' ControllerFactory, Routes) injected explicitly rather than reaching into
' this page's globals itself.
Dim MVC_Instance
Function MVC()
Set MVC = MVC_Instance
End Function

Sub EnsureTestMvc()
Call EnsureTestRouter()
' IsObject(Nothing) is True in VBScript, so - matching EnsureTestRouter()
' above - both conditions are needed: "never assigned" and "explicitly
' reset to Nothing" are different states here.
If (Not IsObject(MVC_Instance)) Then
Set MVC_Instance = GetObject("script:" & ResolveProjectPath("core\\mvc.wsc"))
ElseIf MVC_Instance Is Nothing Then
Set MVC_Instance = GetObject("script:" & ResolveProjectPath("core\\mvc.wsc"))
End If
Set MVC_Instance.Router = router
Set MVC_Instance.ControllerRegistry = ControllerRegistry()
Set MVC_Instance.ControllerFactory = ControllerFactory()
Set MVC_Instance.Routes = Routes()
End Sub

Call ASPUnit.AddModule( _
ASPUnit.CreateModule( _
"MVC Dispatch Smoke Tests", _
Array( _
ASPUnit.CreateTest("RootRouteResolvesToHomeController"), _
ASPUnit.CreateTest("KnownRouteDispatchCompletesWithoutLookupFailure") _
ASPUnit.CreateTest("KnownRouteDispatchCompletesWithoutLookupFailure"), _
ASPUnit.CreateTest("ControllerNameReturnsDispatchedControllerNameAsString"), _
ASPUnit.CreateTest("ControllerNameFeedsUrlGenerationForRedirects") _
), _
ASPUnit.CreateLifeCycle("SetupMvcDispatch", "TeardownMvcDispatch") _
) _
@@ -47,20 +106,29 @@ Call ASPUnit.Run()
Sub SetupMvcDispatch()
Call ResetTestRuntime()
On Error Resume Next
MVC_Dispatcher_Class__Singleton = Empty
TestDispatchController_Class__Singleton = Empty
TitleTestController_Class__Singleton = Empty
Set MVC_Instance = Nothing
On Error GoTo 0
Call ExecuteGlobal("Dim dispatchActionRan")
dispatchActionRan = False
Call RegisterDefaultRoutes()
Call EnsureTestMvc()

Call ControllerRegistry().RegisterController("testdispatchcontroller")
Call router.AddRoute("GET", "/dispatch-smoke", "testdispatchcontroller", "Smoke")
Call ControllerFactory().Register("testdispatchcontroller", GetRef("TestDispatchController"))

Call ControllerRegistry().RegisterController("titletestcontroller")
Call router.AddRoute("GET", "/title-test", "TitleTestController", "Smoke")
Call ControllerFactory().Register("TitleTestController", GetRef("TitleTestController"))
End Sub

Sub TeardownMvcDispatch()
On Error Resume Next
MVC_Dispatcher_Class__Singleton = Empty
Set MVC_Instance = Nothing
TestDispatchController_Class__Singleton = Empty
TitleTestController_Class__Singleton = Empty
Response.Status = "200 OK"
On Error GoTo 0
Call ResetTestRuntime()
@@ -77,4 +145,33 @@ Function KnownRouteDispatchCompletesWithoutLookupFailure()
Call MVC().DispatchRequest("GET", "/dispatch-smoke")
Call ASPUnit.Ok(dispatchActionRan, "Dispatch should reach a registered controller action without whitelist or lookup failures")
End Function

' Regression test: MVC.ControllerName used to do "ControllerName = CurrentController",
' assigning an object to a property return without Set. Reading MVC.ControllerName
' after any controller had been dispatched raised "Object doesn't support this
' property or method" instead of returning a value. This confirms it now returns
' the dispatched controller's name as a plain string, with the "Controller" suffix
' stripped (matching the convention Active() and *StylesheetTag already rely on).
Function ControllerNameReturnsDispatchedControllerNameAsString()
Call MVC().DispatchRequest("GET", "/title-test")

Dim controllerNameValue
controllerNameValue = MVC().ControllerName

Call ASPUnit.Ok((VarType(controllerNameValue) = vbString), "MVC.ControllerName should return a String, not the controller object")
Call ASPUnit.Equal(controllerNameValue, "TitleTest", "MVC.ControllerName should return the dispatched controller's name with the Controller suffix stripped")
End Function

' ControllerStylesheetTag, RedirectToAction, and the CSRF-invalid-token redirect
' helper all build a URL/asset path from MVC.ControllerName. This confirms that
' downstream use actually works now that ControllerName returns a real string,
' without needing to trigger a live Response.Redirect inside the test run.
Function ControllerNameFeedsUrlGenerationForRedirects()
Call MVC().DispatchRequest("GET", "/title-test")

Dim generatedUrl
generatedUrl = Routes.UrlTo(MVC().ControllerName, "Smoke", Empty)

Call ASPUnit.Ok((InStr(LCase(generatedUrl), "/titletest/smoke") > 0), "URL built from MVC.ControllerName should contain the lowercase controller/action path, got: " & generatedUrl)
End Function
%>

+ 1
- 1
tests/integration/web.config Прегледај датотеку

@@ -6,7 +6,7 @@
<add key="CacheExpirationYear" value="2030" />
<add key="EnableCacheBusting" value="false" />
<add key="CacheBustParamName" value="v" />
<add key="ProductionAppBaseUrl" value="http://localhost:8081/" />
<add key="ProductionAppBaseUrl" value="http://localhost:8080/" />
</appSettings>

<system.webServer>


+ 1
- 1
tests/unit/web.config Прегледај датотеку

@@ -6,7 +6,7 @@
<add key="CacheExpirationYear" value="2030" />
<add key="EnableCacheBusting" value="false" />
<add key="CacheBustParamName" value="v" />
<add key="ProductionAppBaseUrl" value="http://localhost:8081/" />
<add key="ProductionAppBaseUrl" value="http://localhost:8080/" />
</appSettings>

<system.webServer>


+ 1
- 1
tests/web.config Прегледај датотеку

@@ -6,7 +6,7 @@
<add key="CacheExpirationYear" value="2030" />
<add key="EnableCacheBusting" value="false" />
<add key="CacheBustParamName" value="v" />
<add key="ProductionAppBaseUrl" value="http://localhost:8081/" />
<add key="ProductionAppBaseUrl" value="http://localhost:8080/" />
</appSettings>

<system.webServer>


Loading…
Откажи
Сачувај

Powered by TurnKey Linux.